Каковы примеры неконтекстно-свободных языков в языке C? Как следующий не-CFL существует на языке C?
а) L1 = {wcw|w равно {a,b}*}
b) L2 = {a^n b^m c^n d^m| n,m >=1}
Каковы примеры неконтекстно-свободных языков в языке C? Как следующий не-CFL существует на языке C?
а) L1 = {wcw|w равно {a,b}*}
b) L2 = {a^n b^m c^n d^m| n,m >=1}
Вопрос коряво сформулирован, поэтому читаю между строк, вот. Тем не менее, это распространенный вопрос о домашнем задании/учебе.
Различные двусмысленности [1] в грамматике C в обычном представлении не делают язык неконтекстно-свободным. (На самом деле, они даже не делают грамматики неконтекстно-свободными.) Общее правило «если это выглядит как объявление, то это объявление независимо от других возможных синтаксических анализов», вероятно, может быть кодифицировано в очень сложной контекстно-свободной грамматике. (хотя не на 100% очевидно, что это правда, поскольку CFG не замкнуты при пересечении или различии), но проще анализировать с помощью более простой CFG, а затем устранять неоднозначность в соответствии с правилом объявления.
Важным моментом в отношении C (и большинства языков программирования) является то, что синтаксис языка немного сложнее, чем BNF, используемый для пояснительных целей. Например, программа на C не будет корректной, если переменная используется без определения. Это синтаксическая ошибка, но анализатор CFG ее не обнаруживает. Грамматические конструкции, необходимые для определения этих случаев, довольно сложны из-за сложного синтаксиса языка, но они сводятся к требованию, чтобы идентификаторы появлялись дважды в допустимой программе. Следовательно, L1 = {wcw|w is {a,b}+}
(здесь w
— это идентификатор, а c
слишком сложно, чтобы его можно было расшифровать). На практике проверка этого требования обычно выполняется с помощью таблицы символов, а правила формального языка, хотя и точны, не написаны в логическом формализме. Поскольку L1
не является контекстно-свободным языком, формализм не может быть контекстно-свободным, но контекстно-зависимая грамматика может распознавать L1
, так что это не совсем невозможно. (См., например, Алгол 68.)
Таблица символов также используется для принятия решения о том, следует ли сократить конкретное identifier
до typedef-name
[2]. Это необходимо для устранения ряда неясностей в грамматике. (Это также дополнительно ограничивает набор строк в языке, потому что в некоторых случаях identifier
должно быть разрешено как typedef-name
, чтобы программа была корректной.)
Для другого типа контекстной зависимости вызовы функций должны совпадать с объявлениями функций по количеству аргументов; такого рода требования моделируются L2 = {a^n b^m c^n d^m| n,m >=1}
, где a
и c
представляют определение и использование некоторой функции, а b
и d
представляют определение и использование другой функции. (Опять же, в очень упрощенной форме.)
Это второе требование, возможно, менее явно является синтаксическим требованием. Другие языки (например, Python) допускают вызовы функций с любым количеством аргументов и обнаруживают совпадение количества аргументов/параметров как семантическую ошибку, обнаруживаемую только во время выполнения. Однако в случае C несоответствие явно является синтаксической ошибкой.
Короче говоря, набор грамматически допустимых строк, составляющих язык C, является правильным подмножеством набора строк, распознаваемых CFG, представленным в определении языка C; набор допустимых синтаксических анализов является правильным подмножеством набора производных, сгенерированных CFG, а сам язык (а) однозначен и (б) не является контекстно-свободным.
Примечание 1: Большинство из них на самом деле не являются двусмысленностями, потому что они зависят от того, как разрешается данное identifier
(имя typedef, идентификатор функции, объявленная переменная,...).
Примечание 2: Это не тот случай, когда identifier
нужно разрешать как typedef-name
, если это так; это происходит только в тех местах, где возможно сокращение. Использование одного и того же идентификатора как для типа, так и для переменной, даже в одной и той же области видимости, не является синтаксической ошибкой. (Это не очень хорошая идея, но допустимая.) В следующем примере, адаптированном из примера из раздела 6.7.8 стандарта, показано использование t
как имени поля, так и определения типа:
typedef signed int t;
struct tag {
unsigned t:4; // field named 't' of type unsigned int
const t:5; // unnamed field of type 't' (signed int)
};
Эти вещи не являются контекстно-свободными в C:
foo * bar; // foo multiplied by bar or declaration of bar pointing to foo?
foo(*bar); // foo called with *bar as param or declaration of bar pointing to foo?
foo bar[2] // is bar an array of foo or a pointer to foo?
foo (bar baz) // is foo a function or a pointer to a function?