Язык Си в примерах/Простая грамматика
- Компиляция программ
- Простейшая программа «Hello World»
- Учимся складывать
- Максимум
- Таблица умножения
- ASCII-коды символов
- Верхний регистр
- Скобочки
- Факториал
- Степень числа
- Треугольник Паскаля
- Корень уравнения
- Система счисления
- Сортировка
- Библиотека complex
- Сортировка на основе qsort
- RPN-калькулятор
- RPN-калькулятор на Bison
- Простая грамматика
- Задача «Расчёт сопротивления схемы»
- Простая реализация конечного автомата
- Использование аргументов командной строки
- Чтение и печать без использования stdio
- Декодирование звукозаписи в формате ADX
- Другие примеры
- XCC C
Грамматики (подмножества множества слов в некотором алфавите ) можно описывать с помощью правил. Каждое правило состоит из левой и правой части, между которыми стоит стрелка ( -> ).
Данную стрелку можно интерпретировать как «представляет собой»
или «состоит из». Например, правило
A -> B C D
можно прочитать как
- «Слово типа A представляет собой слияние слова типа B, слова типа C и слова типа D.»
Но в теории формальных грамматик принята другая терминология:
- «Из символа A можно вывести последовательность символов B, C и D.»
Расcмотрим три правила, в которых присутствует только один символ S (один нетерминальный символ), и три терминальных (то есть нераскладываемых, таких символов, из которых ничего нельзя вывести кроме их самих) символов '0', '1', '2', '3':
S -> '0' S -> '1' S S -> '2' S S S -> '3' S S S
Правило S -> '3' S S S , к примеру, означает, что если a, b и c являются корректными словами (словами, выводимыми из символа S), то и слово 3abc тоже является корректным.
- Примеры корректных слов: 0, 10, 110, 200, 2100, 2010, 1111111110.
- Примеры некорректных слов: 01, 00, 300, 3333, 11120.
Приведённая ниже программа на Си определяет корректность введённого слова.
#include <stdio.h>
#include <limits.h>
int Read()
{
int head, i;
scanf("%d", &head); // Читаем первое число.
if(head < 0 || head > 3) { // Возвращаем код ошибки, если оно не из
return 1; // множества {0, 1, 2, 3}
}
for(i = 0; i < head ; i++) {
if(Read()) {
return 1;
}
}
return 0;
}
int main()
{
if(!Read()) {
int n;
if(scanf("%d", &n) != 0)
printf("incorect\n");
else
printf("correct\n");
} else {
printf("incorrect\n");
}
return 0;
}
Здесь представлен классический рекурсивный способ лексографического разбора.
Программный код можно максимально приблизить к самим правилам:
ReadS() {
if( scanf("%d", &n) != 1 ) return 0;
switch(c) {
'0': return 1;
'1': return ReadS();
'2': return (ReadS() && ReadS() );
'3': return (ReadS() && ReadS() && ReadS());
default: return 0;
}
}
Задание
[править]- Задача 1. Напишите программу, определяющую корректность слова, не используя идеи рекурсии и стека. Для этого обявите переменную n, равную количеству объектов S, которые осталось считать. Изначально n = 1.
- Задача 2. Напишите программу, которая определяет, является ли введённое слово выводимым из символа S.
S -> A | B A -> '(' B* ')' B -> '[' A* ']'
Вертикальная черта «|» означает соединительный союз «или».
Здесь используется специальный символ «*». Это указатель количества. Запись B* означает любое количество слов типа B (слов, выводимых из символа B), либо пустое слово. Другими словами, символ «*» означает 0, 1, 2,... раз. Есть также специальные символы + и ?:
- * -- 0,1, ..., раз.
- + -- 1,2, ..., раз (то есть, любое число раз, но по крайней мере один раз).
- ? -- 0 или 1 раз (то есть либо есть, либо нет).
Примеры слов, выводимых из A:
(), ([]), ([][()()]), ([][][][])
Примеры слов, выводимых из B:
[], [()], [([])([])()], [([][][][])]
Подсказка: определите две функции ReadA и ReadB которые рекурсивно вызывают друг друга.
Код примерно должен быть таким:
ReadChar(char x) {
int c;
if((c=getchar()) != x) {
ungetc(c);
return 0;
} else {
return 1;
}
}
ReadA() {
int c;
if ( !ReadChar( '(' ) ) return 0; // читаем открывающую скобку
while(ReadB()) {}; // читаем B*
if ( !ReadChar( ')' ) ) return 0; // читаем закрывающую скобку
return 1;
}
ReadB() {
... // по аналогии с ReadA
}
ReadS() {
ReadA || ReadB;
if(getchar() != EOF)
printf ("Incorrect\n");
else
printf ("Correct\n");
}
Разбор языков (parsing), которые задаются простыми рекурсивными грамматиками, реализуют с помощью рекурсивных функций, которые возвращают 1 (успешно считано) или 0 (не считано).
- Задача 4. Прочитайте учебный текст про формальные грамматики. Напишите грамматики следующих языков:
- язык записи действительных чисел в языке Си.
- язык записи email адресов.
- язык записи арифметических выражений над целыми числами со скобками и операциями + * - / .
- Задача 5. Просмотрите формальное описание грамматики языка Си. Является ли условие выводимости из символа program необходимым для компиляции программы? Является ли это условие достаточным? Ответ обоснуйте.
- Задача 6. Изучите задачу вычисления сопротивления по описанию параллельно-последовательной электрической схемы из сопротивлений — Язык Си в примерах/Задача «Расчёт сопротивления схемы»