Часть II. Формальное описание языков программирования Формальная спецификация формальных языков

advertisement
Часть II. Формальное описание
языков программирования
(Формальная спецификация формальных
языков)
Введение.
Атрибутные грамматики
План
1.
2.
3.
4.
5.
Цели и задачи формального описания языков
программирования
Синтаксис, семантика, прагматика (статическая и
динамическая семантики)
Подходы к описанию (контекстно-свободного)
синтаксиса
Подходы к описанию контекстно-зависимого
синтаксиса (статической семантики)
Подходы к описанию (динамической) семантики
Цели
1. Автоматическая генерация программ
2. Автоматическая верификация
3. Недвусмысленное (строгое) описание
языка
4. Апробация новых концепций при
разработке новых языков
Задачи формального описания
Описание контекстных зависимостей (статической семантики)
1.
2.
Описать требования к (статически) корректной программе (для пользователя
языка, для разработчика тестов компилятора, для разработчика компилятора)
Сформировать исходные данные для генератора фронт-процессора
Описание «смысла», динамической семантики
1.
Описать эталон, с которым можно сравнивать результаты выполнения
скомпилированной программы
2.
Сформировать исходные данные для генератора кода компилятора
3.
Описать правила (аксиомы) эквивалентности, которые можно использовать для
построения оптимизаторов
4.
Описать правила (аксиомы) эквивалентности, которые можно использовать для
верификации оптимизаторов
Спецификация блоков компилятора и компилятора в целом
1.
Сформировать исходные данные для построения тестов для компилятора в
целом и для отдельных блока компилятора
Сесантический свойства
(аспекты) языка
1. Передача параметров
2. Порядок вычислений параметров функций
3. Области видимости имен
4. Размещение данных в памяти
5. Обработка исключений, более широко,
поведение при ненормальном ходе вычислений
6. Синхронизация и взаимодействие
параллельных ветвей (нитей) программы
7. Динамическое связывание программ
8. Сбор мусора
Другие приложения
формальных описаний языков
1. Разработка и тестирование протокольных
сообщений (язык построения заголовков)
2. Генерация html документов, тестирование
процессоров html документов, например,
браузеров.
3. Генерация XML документов, тестирование
процессоров XML документов
4. Какие еще области используют формальные
языки?
Синтаксис, семантика (статическая
и динамическая), прагматика
• Синтаксис - структура,
• Семантика
– статическая - корректность
– динамическая - смысл,
• Прагматика – назначение
Язык и грамматика
Язык 1 – множество предложений.
Грамматика – множество правил, которые описывают порождение
предложений языка.
Язык 2 – множество предложений, которые порождаются из данной
грамматики.
Синтаксис – описание структуры предложений.
Структура – набор элементов и связей между ними.
Каждому слову сопоставляется его (грамматическая) роль и задаются
связи между словами, между группами слов (фразами).
Контекстно-свободные грамматики, контекстно-свободный
синтаксис.
Контекстно-зависимый синтаксис (context-sensitive, статическая
семантика)
Грамматика
• Алфавит: конечный набор символов Σ
• Строки: конечные цепочки символов
• Пустая строка ε
• Σ* - множество всех возможных строк из символов
Σ (включая ε)
• Σ+ - множество всех непустых строк из символов Σ
• Язык: множество строк L  Σ*
Грамматики
G = (N, T, S, P)
Конечное множество нетерминальных символов N
Конечное множество терминальных символов T
Стартовый нетерминальный символ S  N
Конечное множество продукций P
Продукция: x → y
x  (N T)+, y  (N T)*
Применение продукции: uxv  ayb
Языки и грамматики
•
•
•
•
•
Вывод строки
w1  w2  …  wn; обозначается как w1  wn
Язык, которые генерируется грамматикой
L(G) = { w  T* | S  w }
Традиционная классификация
–
–
–
–
Регулярные (Regular)
Контекстно-свободные (Context-free)
Контекстно-зависимые (Context-sensitive)
Все остальные (Unrestricted)
Контекстно-свободные
(КС) языки
• Выход за границы регулярных языков
– L = { anbn | n > 0 } КС, но не регулярный
• Генерируется КС грамматикой
• Все продукции имеют вид: A → w, где
– A  N, w  (N  T)*
• BNF:
– Backus-Naur form: John Backus and
PeterNaur, for ALGOL60
Пример расширенной BNF
(EBNF)
<stmt> ::= while <exp> do <stmt>
| if <exp> then <stmt> [ else <stmt> ]
| <exp> := <exp>
| <id> ( <exp> {, <exp> } )
Введены сокращения:
• [ … ] - цепочка, которая может быть
опущена
• { … } - цепочка, которая может
отсутствовать или повторяться некоторое
число раз
Дерево вывода
• Другие названия: дерево разбора (parse
tree) или дерево конкретного синтаксиса
(concrete syntax tree)
– Листья - терминалы
– Внутренние узлы - нетерминалы
– Корень – стартовый нетерминал грамматики
• Описывает конкретный путь вывода
заданной строки
– Узлы-листья слева направо составляют
заданную входную цепочку
• обход графа «сначала вглубь», начиная с самой
левой непройденной вершины
Пример дерева вывода
Ограничения КС
грамматик
• Не могут представлять семантику,
например
– «каждая переменная должна быть описана
ранее, чем ее используется»
– «использование переменной должно быть
согласовано с ее типом»
– не описывает действия, которые скрываются за
тексом, например “«строка» s1 делится на
«строку» s2”
• Решение: атрибутные грамматики
– Определенный вид описания семантики
Атрибутные грамматики
• КС грамматика (BNF)
• Конечное множество атрибутов
– Для каждого атрибута – область возможных
значений
– Для каждого терминала и нетерминала –
можесвто ассоциированных с ним атрибутов
(возможно, пустое)
• Наследуемые (inherited) и синтезируемые
(synthesized)
• Множество правил вычисления
атрибутов
• Множество булевских условий, заданных
Пример
• L = { anbncn | n > 0 } – не КС
• BNF
<start> ::= <A><B><C> <A> ::= a | a<A>
<B> ::= b | b<B> <C> ::= c | c<C>
• Атрибуты
–
–
–
–
Na: ассоциирован с <A>
Nb: ассоциирован с <B>
Nc: ассоциирован с <C>
Область значений = integers
Пример
• Правила вычисления (аналогично для <B>, <C>)
<A> ::= a
Na(<A>) := 1
| a<A>2
Na(<A>) := 1 + Na(<A>2)
• Условия
<start> ::= <A><B><C>
Cond: Na(<A>) = Nb(<B>) = Nc(<C>)
• Альтернативная нотация - <A>.Na
Дерево разбора
Дерево разбора для некоторой
атрибутной грамматики
• Корректное дерево для опорной
(underlying) BNF
• Каждый узел имеет свой набор пар
(атрибут-значение)
• Некоторые узлы имеют булевские условия
• Корректное дерево разбора
– Значения атрибутов отвечают правилам их
вычисления
– Все булевские атрибуты (условия) истинны
Пример – блок в языке Ada
x: begin a := 1; b := 2; end x;
• <block> ::= <block id>1 : begin
<stmts> end <block id>2 ;
– Cond: value(<block id>1) = value(<block id>2)
• <stmts> ::= <stmt> | <stmts> <stmt>
• <block id> ::= id
– value(<block id>) := id
Другой способ задания
условий
<block>.OK :=
<block id>1.value = <block id>2.value
Все узлы типа <block> должны иметь
<block>.OK = true
Синтезируемые vs.
наследуемые атрибуты
• Синтезируемые атрибуты вычисляются, используя
значения из деревьев-потомков
– Продукция - <A> ::= …
– Правило вычисления - <A>.syn := …
• Наследуемые – значения из узла-родителя
– Продукция - <B> ::= … <A> …
– Правило вычисления - <A>.inh := …
• В обоих случаях правила вычисления могут иметь
произвольную сложность
Синтезируемые и
наследуемые атрибуты
inh
syn
Правила вычислений
• Синтезируемые атрибуты ассоциированные
с N:
– каждая альтернатива в продукции для N должна
иметь правило для вычисления такого атрибута
• Наследуемые атрибуты ассоциированные с
N:
– для каждого вхождения N в правой части любой
альтернативы должно быть правило для
вычисления этого атрибута
Пример: двоичные числа
КС грамматика
Для простоты будем писать X вместо <X>
B ::= D
B ::= D B
D ::= 0
D ::= 1
Цель – вычислить значение двоичного
числа
Пример: двоичные числа (2)
B ::= D
B.pos := 1
B.val := D.val
D.pow := 0
B1 ::= D B2
•ADD attributes
•B: syn val
•B: syn pos
•D: inh pow
•D: syn val
B1.pos := B2.pos + 1
B1.val := B2.val + D.val
D.pow := B2.pos
D ::= 0
val := 0
D ::= 1
D.val := 2D.pow (возведение в степень)
Дерево разбора с
вычисленными атрибутами
КС грамматика
<prog> ::= <block>
<block> ::= begin <decls> ; <stmts> end
<stmts> ::= <stmt>
| <stmt> ; <stmts>
<stmt> ::= <assign>
| <block>
| ...
Задачи
• Проверить типы переменных (int,
bool)
• Для вложенных блоков использовать
самые внутренние декларации (static
scoping)
• Проверять типы параметров и тип
возвращаемого значения в функциях
– Например, все функции имеют тип
результата int
Дерево разбора
prog
|
block
Декларации
Операторы
Структура данных
• Использовать стек групп пар вида имятип
– стек «таблиц символов»
• Строить таблицы символов для
деклараций в блоках
– синтезируемый атрибут tbl
• Использовать стек таблиц символов в
операторах и выражениях
– наследуемый атрибут symtab
Пример – проверка типов
begin
bool i;
int j;
begin
int i;
x := i + j;
end
end
Верхушка стека
[
{("i",INT)},
{("i",BOOL), ("j",INT)}
]
База стека
Атрибутная грамматика
<prog> ::= <block>
<block>.symtab := emptystack
<block> ::= begin <decls> ; <stmts> end
<stmts>.symtab :=
push(<decls>.tbl, <block>.symtab)
<stmts>1 ::= <stmt>
<stmt>.symtab := <stmts>1.symtab
|
<stmt> ; <stmts>2
<stmt>.symtab := <stmts>1.symtab
<stmts>2.symtab := <stmts>1.symtab
Декларации
<decls>1 ::= <decl>
<decls>1.tbl := <decl>.tbl
| <decl> ; <decls>2
<decls>1.tbl :=
<decl>.tbl  <decls>2.tbl
Условие:
ids(<decl>.tbl) ∩ ids(<decls>2.tbl) = 
ids – функция, которая получает множество
вида имя-тип и возвращает множество
всех имен
Декларации
<decl> ::= int <id>
<decl>.tbl := { (<id>.name, INT) }
| bool <id>
<decl>.tbl := { (<id>.name, BOOL) }
| fun <id> ( <params> ) : int =
<block>
<decl>.tbl :=
{ (<id>.name,
FUN(<params>.types, INT) ) }
<block>.symtab := Oops!…….
Синтезируемый
атрибут упорядоченный
список INT/BOOL
Возврат к <decls>.symtab
• Декларации добавляются в наследуемый
атрибут symtab
<block> ::= begin <decls> ; <stmts> end
<stmts>.symtab :=
push(<decls>.tbl, <block>.symtab)
<decls>.symtab :=
push(<decls>.tbl, <block>.symtab)
Сначала берем декларации в <decls>,
потом используем их для проверок блоков
встроенных в <decls>
Декларации
<decls>1 ::= <decl>
<decls>1.tbl := <decl>.tbl
|
<decl> ; <decls>2
<decls>1.tbl :=
<decl>.tbl  <decls>2.tbl
<decl>.symtab := <decls>1.symtab
<decls>2.symtab := <decls>1.symtab
Cond:
ids(<decl>.tbl) ∩ ids(<decls>2.tbl) = 
Декларации
<decl> ::= int <id>
<decl>.tbl := { (<id>.name, INT) }
| bool <id>
<decl>.tbl := { (<id>.name, BOOL) }
| fun <id> ( <params> ) : int = <block>
<decl>.tbl :=
{ (<id>.name,
FUN(<params>.types, INT) ) }
<block>.symtab :=
push(<params>.tbl,<decl>.symtab)
Проверка типов в теле
функции
fun f (int i): int =
begin
... g(5) ...
end;
fun g (int j): int =
begin
...
end
Операторы
<stmts>1 ::= <stmt>
<stmt>.symtab := <stmts>1.symtab
| <stmt> ; <stmts>
<stmt>.symtab := <stmts>1.symtab
<stmts>2.symtab := <stmts>1.symtab
<stmt> ::= <assign>
<assign>.symtab := <stmt>.symtab
| <block>
<block>.symtab := <stmt>.symtab
| if <boolexp> then <stmts> else ...
<boolexp>.symtab := <stmt>.symtab
<stmts>.symtab := <stmt>.symtab
Операторы
<assign> ::= <id> := <intexp>
<intexp>.symtab := <assign>.symtab
typeof(<id>.name,<assign>.symtab) = INT
| <id> := <boolexp>
<boolexp>.symtab := <assign>.symtab
typeof(<id>.name,<assign>.symtab) = BOOL
Выражения
<intexp>1 ::= <integer>
Cond: typeof(<id>.name,
<intexp>1.symtab) = INT
| <intexp>2 + <intexp>3
<intexp>2.symtab := <intexp>1.symtab
<intexp>3.symtab := <intexp>1.symtab
| false
Cond: typeof(<id>.name,
<boolexp>.symtab) = BOOL
Вызов функции (Function Call)
<intexp> ::= <id> ( <args> )
Cond:
typeof(<id>.name,<intexp>.symtab) = FUN
Cond:
rettype(<id>.name,<intexp>.symtab) = INT
<args>.expTypes :=
paramtypes(<id>.name,<intexp>.symtab)
<args>.symtab := <intexp>.symtab
Резюме по атрибутным
грамматикам (1)
АГ хороши для спецификации контекстносвободных языков
Техника построена комбинации вычислений
синтезируемых и наследуемых атрибутов
Последовательность вычислений атрибутов
может определяться автоматически
Имеется возможности для для отсеивания
некорректных деревьев разбора
Резюме по атрибутным
грамматикам (2)
“Глобальные” структуры данных (окружение )
передается через атрибуты
“Глобальные” счетчики (например, метки)
реализуются как пары атрибутов
Абстрактные типы данных (например, множества)
используются до тех пор, пока не начинается
собственно программирование
Правила могут использовать вспомогательные
функции
Реальные применения: для проверки типов
(type checking) и генерации кода (code
generation)
Литература
1. Formal specification of programming languages,
F.Pagan
2. Formal syntax and semantics of programming
languages, Kurtz and Slonnegar.
Download