Написав предыдущий выпуск, я хотел обратить внимание на то, что программный код – нечто более гибкое, чем кажется. Однако это вызвало некоторую критику в комментариях. Я обработаю критику и заодно расскажу про синтаксические анализаторы.
Итак, если вы говорите, что "не во всех языках так", то вы пропускаете мимо основной смысл. Говорить, что нечто конкретное можно или нельзя в конкретном языке – значит возвращаться в ловушку жёстких определений, от которой, по идее, и предполагалось уйти.
Как понимать смысл данного материала? Не "это можно" или "то нельзя", а
Разрешено всё, что не запрещено
И чтобы понять, что именно не запрещено, нужно обратиться к принципам работы синтаксического анализатора.
У любого языка есть синтаксис: свод правил, в соответствии с которым соединяются "части речи" этого языка. Перед тем как программа исполняется, она пропускается через синтаксический анализатор, который анализирует, всё ли правильно.
Синтаксический анализатор не исполняет программу – он только проверяет синтаксис.
Теоретически нужен только один синтаксический анализатор для любого или почти любого языка. Каким образом, если у разных языков синтаксис разный?
Всё дело в самом анализаторе. Он анализирует не язык, а правила, которые задаются с помощью универсальной схемы. Всё, что можно описать такой схемой – может быть успешно проверено анализатором.
А всё, что успешно проверено – будет работать
Схема содержит в себе определения всех частей языка – из чего они состоят, как соединяются между собой. Из мелких определений строятся крупные.
Самым крупным определением является
программа
Да, вот так просто. То есть проанализировав некий произвольный текст, анализатор должен сделать вывод, программа это или нет.
Дальше определение программа нужно развернуть:
программа: инструкция [инструкция]
Значит, это будет программа, если анализатор найдёт там инструкция. Обратите внимание на вторую инструкция в квадратных скобках. Квадратные скобки указывают, что это необязательная часть. Она может либо отсутствовать, либо повторяться сколько угодно раз.
C помощью такого описания мы заявили, что программа – это одна или больше инструкция, что анализатор и проверит. Но для этого ему, конечно, нужно обеспечить определение инструкция:
инструкция: выражение | цикл | условие
Знак "|" в данной схеме означает "или". Мы сообщаем, что инструкция является одним из: выражение, цикл, условие. На деле гораздо больше, но для примера хватит. Понятно, что каждое из этих определений должно быть в свою очередь задано. Рассмотрим подробнее выражение, но начнём с противоположного конца, с самых примитивных определений:
цифра: 0..9
буква: A..Z | a..z | _
число: цифра [цифра]
имя: буква [буква | цифра]
операнд: число | имя
знак_операции: == | > | < | + | - | * | /
Следуя вышеописанным обозначениям, здесь мы задали, что
- цифра это символ от 0 до 9
- буква это символ от A до Z, либо от a до z, либо _ (знак подчеркивания)
- число это одна или более цифра
- имя это одна буква и любое количество буква или цифра
- операнд это или имя (переменной), или число
- знак_операции это "==" (сравнить), ">" (больше), "+" (сложить), ну и так далее. Здесь тоже перечислены не все возможные операции.
Теперь мы можем записать определение выражения:
выражение: операнд [знак_операции выражение]
Из чего можно сделать следующие выводы:
- Выражение может состоять из одного операнда
- Выражение может состоять из операнда, знака операции и другого выражения
- Выражение может содержать другие выражения, то есть его определение рекурсивно.
В соответствии с данной схемой я приведу выражения, которые можно просто так написать в предполагаемой программе, и они будут корректными. Обращаю внимание: они не обязаны быть полезными, осмысленными, лучше/хуже, проще/сложнее, они просто корректные:
5
a
4 == 1
a * 5
a > b * 5 == c / 2 < 3
Это не запрещено.
Для дальнейших нужд я доработаю выражение, добавив в него присваивание:
присваивание: имя = (выражение | присваивание)
выражение: присваивание | (операнд [знак операции выражение])
И теперь можно писать любые корректные присваивания:
a = 5
a = b * 5
a = b = c == d
a == b = 5
Замечу, что определение выражения в принципе можно задать по-разному. Но я сейчас пишу это всё на ходу из головы, поэтому как получится, так и получится.
Пример цикла for
Если, допустим, мы считали, что цикл for в C-подобных языках нужен только для циклов, то посмотрев на его определение, можно найти кое-что интересное. Да, кто-то это и так знает, и это "рассказывают в школе на первом уроке", но всё же:
цикл_for: for (выражение; выражение; выражение) [инструкция | блок_инструкций]
Как видим, все три параметра цикла for это просто выражения. То есть там мы можем записать всё, что является выражением. Никто никогда не обязывал нас писать только подобные конструкции:
for (i = 0; i < 10; i++);
Вывод: делать из for именно цикл совершенно необязательно, и всё, что вы там напишете, будет работать ровно настолько, насколько оно соответствует синтаксической схеме.
Ошибка в if()
Одна из распространённых ошибок начинающих программистов в C-подобных языках:
if (a = 5) print "a = 5";
Что они хотят: проверить, равна ли переменная a числу 5 (то есть правильно писать так: a == 5). Что происходит на самом деле: они внутри if присваивают a = 5. Почему это возможно? Потому что:
условие: if (выражение) инструкция | блок_инструкций
И a = 5, и a == 5 являются в нашей схеме выражением и имеют свои значения:
выражение a = 5 имеет значение 5
выражение a == 5 имеет значение либо true, либо false.
Соответственно условие if будет фактически выглядеть так:
if (5) или допустим if (true)
В других языках цикл for или условие if могут не позволить так сделать, потому что они описаны по другой схеме. Но это же неважно. Важно, что работает всё, что не противоречит схеме. В любом языке. А что не противоречит – всегда можно найти и понять.
Для этого нужно или взять документацию по языку и посмотреть на его синтаксическую схему, или просто поэкспериментировать – если вы чуете, что где-то что-то можно написать, то просто попробуйте это сделать, и проверите своё чутьё. Если всё получилось – значит, вас можно поздравить с тем, что вы правильно понимаете синтаксис языка и можете предсказывать его конструкции, даже не зная о них.
Финальный вопрос:
В чём смысл, польза, кому это надо?
Смысл и польза бывают редко, но бывают. Но в основном это надо не для того, чтобы программировать что-нибудь забористое, а чтобы не сомневаться – уверен, не уверен, можно, нельзя и т.д. Понимание правил синтаксиса даёт однозначное понимание поведения программы и устраняет сомнения. Вот для этого оно и надо.
В следующий раз я более подробно расскажу про синтаксический разбор выражений.
Читайте дальше: