470 likes | 692 Views
Часть II. Формальное описание языков программирования ( Формальная спецификация формальных языков ). Введение. Атрибутные грамматики. План. Цели и задачи формального описания языков программирования Синтаксис, семантика, прагматика (статическая и динамическая семантики)
E N D
Часть II. Формальное описание языков программирования(Формальная спецификация формальных языков) Введение. Атрибутные грамматики
План • Цели и задачи формального описания языков программирования • Синтаксис, семантика, прагматика (статическая и динамическая семантики) • Подходы к описанию (контекстно-свободного) синтаксиса • Подходы к описанию контекстно-зависимого синтаксиса (статической семантики) • Подходы к описанию (динамической) семантики
Цели • Автоматическая генерация программ • Автоматическая верификация • Недвусмысленное (строгое) описание языка • Апробация новых концепций при разработке новых языков
Задачи формального описания Описание контекстных зависимостей (статической семантики) • Описать требования к (статически) корректной программе (для пользователя языка, для разработчика тестов компилятора, для разработчика компилятора) • Сформировать исходные данные для генератора фронт-процессора Описание «смысла», динамической семантики • Описать эталон, с которым можно сравнивать результаты выполнения скомпилированной программы • Сформировать исходные данные для генератора кода компилятора • Описать правила (аксиомы) эквивалентности, которые можно использовать для построения оптимизаторов • Описать правила (аксиомы) эквивалентности, которые можно использовать для верификации оптимизаторов Спецификация блоков компилятора и компилятора в целом • Сформировать исходные данные для построения тестов для компилятора в целом и для отдельных блока компилятора
Семантические свойства (аспекты) языка • Передача параметров (по значению, по ссылке,...) • Порядок вычислений параметров функций • Области видимости имен • Размещение данных в памяти • Обработка исключений, более широко, поведение при ненормальном ходе вычислений • Синхронизация и взаимодействие параллельных ветвей (нитей) программы • Динамическое связывание программ • Сбор мусора
Другие приложения формальных описаний языков • Разработка и тестирование протокольных сообщений (язык построения заголовков) • Генерация html документов, тестирование процессоров html документов, например, браузеров. • Генерация XML документов, тестирование процессоров XML документов • Какие еще области используют формальные языки?
Синтаксис, семантика (статическая и динамическая), прагматика • Синтаксис - структура, • Семантика • статическая - корректность • динамическая - смысл, • Прагматика – назначение
Язык и грамматика Язык 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
Языки и грамматики • Вывод строки • w1w2… wn; обозначается какw1wn • Язык, которые генерируется грамматикой • 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) илидерево конкретного синтаксиса (concretesyntax 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) • ADD attributes • B: syn val • B: syn pos • D: inh pow • D: syn val B ::= D B.pos := 1 B.val := D.val D.pow := 0 B1 ::= D B2 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>2 <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> | <id> Cond: typeof(<id>.name, <intexp>1.symtab) = INT | <intexp>2+<intexp>3 <intexp>2.symtab := <intexp>1.symtab <intexp>3.symtab := <intexp>1.symtab <boolexp> ::= true | false | <id> 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