Интерпретируемый язык программирования

Содержание

Введение

Я не эксперт. Когда я начал работу над этим проектом, я понятия не имел, что делаю, и всё еще не имею. Я никогда целенаправленно не изучал принципы создания языка — только прочитал некоторые материалы в Сети и даже в них не нашёл для себя почти ничего полезного.


Тем не менее, я написал абсолютно новый язык. И он работает. Наверное, я что-то делаю правильно.

В этой статье я постараюсь показать, каким образом Pinecone (и другие языки программирования) превращают исходный код в то, что многие считают магией

Также я уделю внимание ситуациям, в которых мне приходилось искать компромиссы, и поясню, почему я принял те решения, которые принял

Текст точно не претендует на звание полноценного руководства по созданию языка программирования, но для любознательных будет хорошей отправной точкой.

Java

Язык Java — это байткодовый язык. Иногда его называют «языком разочарований».  Его активно рекламирует компания Oracle. Когда-то он имел слоган «написано однажды — исполняется везде». В силу колоссальной рекламы этого языка он очень востребован, но реклама явно преувеличивает его возможности. На практике программы на этом языке работают в 5-7 раз медленнее, чем программы на C, при этом потребляют памяти в 10-30 раз больше. Кроме того, из-за постоянного доводки и доработки виртуальной машины Java (JVM) Java-программы часто сбоят

Для больших компаний это не столь важно, но для маленьких компаний это может стать катастрофой, поэтому выбирать язык Java можно только в том случае, если это требуется по работе

Также Java является официальным языком платформы Андроид, что также способствует его востребованности.

Так как язык Java активно продвигается крупной IT-компанией, то никаких проблем с его изучением нет. Книги, справочники, курсы, семинары, конференции — это все в наличии. Только бери и учи.

Из чего состоит программа на C++

Начнём с самой первой строчки:

С помощью языка программирования можно:

  • писать условия;
  • работать с памятью;
  • создавать подпрограммы;
  • работать с классами, структурами и объектами;
  • выполнять базовые математические операции и так далее.

Но чтобы получить больше возможностей, программе нужно как-то взаимодействовать с операционной системой. Писать код, который будет отправлять команды ОС, достаточно сложно, но нам и не придётся — всё уже написано другими разработчиками и помещено в специальную библиотеку.

Одна из таких библиотек, iostream, позволяет запрашивать пользовательский ввод или выводить что-то в консоли.

Есть и другие библиотеки, которые помогают работать с графикой, отправлять запросы через интернет, воспроизводить звук и так далее. В будущем вы научитесь самостоятельно писать свои собственные библиотеки.

Вернёмся к коду:

main () — это подпрограмма (функция), с которой начинается выполнение любой программы на C++. Её также называют точкой входа.

Подробнее подпрограммы мы разберём в следующих статьях. Сейчас нужно знать только то, что все команды записывают в тело функции.


Настало время понять, как работают команды:

Команда cout говорит компьютеру о том, что нужно вывести определённый текст в консоли. В нашем случае — Hello, World!. Любой текст должен быть в кавычках, иначе компилятор воспримет его как идентификатор, то есть имя команды, функции, переменной и так далее. Поэкспериментируйте с текстом, запустите программу и посмотрите, что изменится.

После каждой команды должна стоять точка с запятой — она говорит компилятору, что это конец инструкции и нужно переходить к следующей.

Недостатки

Основным недостатком является более медленное выполнение программы по сравнению с выполнением программы, предварительно скомпилированной в машинный код. Например выполнение PHP и Python может оказаться в более чем 100 раз медленнее чем C++. Трансляция в байт-код и JIT-компиляция не решают этой проблемы полностью. Дополнительный слой интерпретатора или виртуальной машины замедляет выполнение программы и может требовать больше ресурсов. Во время выполнения интерпретатор всегда должен быть загружен в память (а это могут быть и большие программы, как браузер для JS или Office для VBA). Комментарии могут снижать производительность и для обхода этого создают две версии кода — готовую для использования (с удалёнными комментариями) и разрабатываемую.

В результате в среднем интерпретируемый код следует тестировать тщательнее компилируемого, строже придерживаться соглашений по оформлению программ и использовать дополнительные анализаторы качества кода. Последний недостаток выражен несильно, так как при серьезной разработке на компилируемых языках также необходимо применение этих средств.

Как CPython выполняет программы

Интерпретатор «Питона» выполняет любую программу поэтапно.

Поэтапное выполнение Python программы Интерпретатором

Этап #1. Инициализация

После запуска вашей программы, Python-интерпретатор читает код, проверяет форматирование и синтаксис. При обнаружении ошибки он незамедлительно останавливается и показывает сообщение об ошибке.

Помимо этого, происходит ряд подготовительных процессов:

  • анализ аргументов командной строки;
  • установка флагов программы;
  • чтение переменных среды и т.д.

Этап #2. Компиляция

Интерпретатор транслирует (переводит) исходные инструкции вашей программы в байт-код (низкоуровневое, платформонезависимое представление исходного текста). Такая трансляция необходима в первую очередь для повышения скорости – байт-код выполняется в разы быстрее, чем исходные инструкции.

Этапы генерации байт-кода из исходного кода на Python

Если Python-интерпретатор обладает правом записи, он будет сохранять байт-код в виде файла с расширением . Если исходный текст программы не изменился с момента последней компиляции, при следующем запуске вашей программы, Python сразу загрузит файл , минуя этап компиляции (тем самым ускорит процесс запуска программы).

Этап #3. Выполнения

Как только байт-код скомпилирован, он отправляется на виртуальную машину Python (PVM). Здесь выполняется байт-код на PVM. Если во время этого выполнения возникает ошибка, то выполнение останавливается с сообщением об ошибке.

PVM является частью Python-интерпретатора. По сути это просто большой цикл, который выполняет перебор инструкций в байт-коде и выполняет соответствующие им операции.

Примечания

  1. Дорот В. Л., Новикав Ф. А. // Толковый словарь современной компьютерной лексики. — 3-е изд. — СПб.: БХВ-Петербург, 2004. — С. 215. — 608 с. — ISBN 9785941574919. — ISBN 5941574916.
  2. Макарова Н. В., Волков В. Б. // Информатика: Учебник для вузов. — СПб.: Питер, 2015. — С. 557. — 576 с. — ISBN 9785496015509.
  3. Microsoft Press. interpret, interpreted language // Толковый словарь по вычислительной технике. — М.: Русская редакция, 1995. — С. 236. — 496 с. — ISBN 5750200086. — ISBN 1556155972.
  4. ↑ I.153 interpretative language // Толковый словарь по вычислительным систамам / Под ред. В. Иллингуорта и др.. — М.: Машиностроение, 1990. — С. 241-242. — 560 с. — ISBN 521700617X.
  5. Кочергин В. И. interpreter // Большой англо-русский толковый научно-технический словарь компьютерных информационных технологий и радиоэлектроники. — 2016. — ISBN 978-5-7511-2332-1.
  6. ↑ Интерпретатор, Интерпретация // Толковый словарь по информатике / Под. ред. Г.Г. Пивняка. — Днепропетровск: Национальный горный университет, 2008. — С. 327-328. — 599 с. — ISBN 978-966-350-087-4.
  7. Воройский Ф. С. // Информатика. Энциклопедический словарь-справочник. — М.: Физматлит, 2006. — С. 325. — 768 с. — ISBN 5922107178. — ISBN 9785457966338.
  8. , 25.3. Где искать жир и патоку? Интерпретируемые языки, с. 585.
  9. , 32.4. Советы по эффективному комментированию. Производительность не является разумной причиной отказа от комментирования, с. 774.
  10. ↑ .
  11. Пратт Т., Зелковиц М. 2.1.3 Трансляторы и виртуальная архитектура // Языки программирования: разработка и реализация. — СПб.: Питер, 2002. — 688 с. — ISBN 5318001890.

История

В ранние годы развития программирования на языки сильно влиял выбор способа выполнения. Например, компилируемые языки требовали задания типа данных переменной в момент её описания или первого использования. В то время как интерпретируемые языки в силу своей динамической природы позволяли отказаться от этого требования, что давало больше гибкости и ускоряло разработку.

Изначально интерпретируемые языки преобразовывались в машинный код построчно, то есть каждая логическая строка компилировалась непосредственно перед выполнением. В результате каждая инструкция, заключенная в тело цикла и исполняемая несколько раз, столько же раз обрабатывалась транслятором. В настоящее время такие эффекты редки. Большинство интерпретируемых языков предварительно транслируются в промежуточное представление. Оно представляет собой байт-код или шитый код (threaded code). Это набор инструкций по вызову небольших фрагментов более низкоуровневого кода, эквивалентный нескольким командам ассемблера или командам виртуальной машины соответственно. Уже этот код исполняется интерпретатором или виртуальной машиной.

Например, такую схему используют следующие языки:

  • Java
  • Python
  • Ruby (использует представление кода в виде абстрактного синтаксического дерева)

Промежуточный код может создаваться как явной процедурой компиляции всего проекта (Java), так и скрытой трансляцией каждый раз перед началом выполнения программы (Perl, Ruby) и при изменении исходного кода (Python).

Виды компиляторов

  • Векторизующий. Базируется на трансляторе, транслирующем исходный код в машинный код компьютеров, оснащённых векторным процессором.
  • Гибкий. Сконструирован по модульному принципу, управляется таблицами и запрограммирован на языке высокого уровня или реализован с помощью компилятора компиляторов.
  • Диалоговый. См.: диалоговый транслятор.
  • Инкрементальный. Пересобирает программу, заново транслируя только изменённые фрагменты программы без перетрансляции всей программы.
  • Интерпретирующий (пошаговый). Последовательно выполняет независимую компиляцию каждого отдельного оператора (команды) исходной программы.
  • Компилятор компиляторов. Транслятор, воспринимающий формальное описание языка программирования и генерирующий компилятор для этого языка.
  • Отладочный. Устраняет отдельные виды синтаксических ошибок.
  • Резидентный. Постоянно находится в оперативной памяти и доступен для повторного использования многими задачами.
  • Самокомпилируемый. Написан на том же языке программирования, с которого осуществляется трансляция.
  • Универсальный. Основан на формальном описании синтаксиса и семантики входного языка. Составными частями такого компилятора являются: ядро, синтаксический и семантический загрузчики.

Варианты компиляции

Вы, наверное, спросите, откуда взялся интерпретатор, если я изначально определил Pinecone как компилируемый язык. Дело в том, что компиляция гораздо сложнее, чем интерпретация — я уже упоминал ранее, что столкнулся с некоторыми проблемами на этом шаге.

Написать свой компилятор

Сначала мне понравилась эта мысль — я люблю делать вещи сам, к тому же давно хотел изучить язык ассемблера. Вот только создать с нуля кроссплатформенный компилятор — сложнее, чем написать машинный код для каждого элемента языка. Я счёл эту идею абсолютно не практичной и не стоящей затраченных ресурсов.

LLVM

LLVM — это коллекция инструментов для компиляции, которой пользуются, например, разработчики Swift, Rust и Clang. Я решил остановиться на этом варианте, но опять не рассчитал сложности задачи, которую перед собой поставил. Для меня проблемой оказалось не освоение ассемблера, а работа с огромной многосоставной библиотекой.

Транспайлинг


Мне всё же нужно было какое-то решение, поэтому я написал то, что точно будет работать: транспайлер (transpiler) из Pinecone в C++ — он производит компиляцию по типу «исходный код в исходный код», а также добавил возможность автоматической компиляции вывода с GCC. Такой способ не является ни масштабируемым, ни кроссплатформенным, но на данный момент хотя бы работает почти для всех программ на Pinecone, это уже хорошо.

Дальнейшие планы

Сейчас мне не достаёт необходимой практики, но в будущем я собираюсь от начала и до конца реализовать компилятор Pinecone с помощью LLVM — инструмент мне нравится и руководства к нему хорошие. Пока что интерпретатора хватает для примитивных программ, а транспайлер справляется с более сложными.

Литература

  • Интерпретация языка структурная // Энциклопедия кибернетики / Глушков В. М.. — Киев: Главная редакция УСЭ, 1974. — Т. 1. — С. 390-391. — 608 с.
  • Кнут Д. Э. 1.4.3 Программы-интерпретаторы // Искусство программирования. Том 1. Основные алгоритмы = The Art of Computer Programming. Volume 1. Fundamental Algorithms / под ред. С. Г. Тригуб (гл. 1), Ю. Г. Гордиенко (гл. 2) и И. В. Красикова (разд. 2.5 и 2.6). — 3. — Москва: Вильямс, 2002. — Т. 1. — 720 с. — ISBN 5-8459-0080-8.
  • Макконнелл С. Совершенный код. — М.: Русская редакция, 2010. — 896 с. — ISBN 9785750200641.
  • Роберт У. Себеста. 1.7.2 Чистая интерпретация // Основные концепции языков программирования. — 5-е изд. — М.: Вильямс, 2001. — С. 50-52. — 672 с. — ISBN 5845901928.
  • Michael L. Scott. Programming Language Pragmatics. — Morgan Kaufmann, 2000. — ISBN 1558604421.

Цель данной статьи:

В данной статье я хочу рассказать о том, как происходит компиляция программ, написанных на языке C++, и описать каждый этап компиляции. Я не преследую цель рассказать обо всем подробно в деталях, а только дать общее видение. Также данная статья — это необходимое введение перед следующей статьей про статические и динамические библиотеки, так как процесс компиляции крайне важен для понимания перед дальнейшим повествованием о библиотеках.

Все действия будут производиться на Ubuntu версии 16.04. Используя компилятор g++ версии:

Состав компилятора g++

  • cpp — препроцессор
  • as — ассемблер
  • g++ — сам компилятор
  • ld — линкер

Мы не будем вызывать данные компоненты напрямую, так как для того, чтобы работать с C++ кодом, требуются дополнительные библиотеки, позволив все необходимые подгрузки делать основному компоненту компилятора — g++.

Зачем нужно компилировать исходные файлы?

Исходный C++ файл — это всего лишь код, но его невозможно запустить как программу или использовать как библиотеку. Поэтому каждый исходный файл требуется скомпилировать в исполняемый файл, динамическую или статическую библиотеки (данные библиотеки будут рассмотрены в следующей статье).

Первые шаги

«А с чего вообще начинать?» — вопрос, который другие разработчики часто задают, узнав, что я пишу свой язык. В этой части постараюсь подробно на него ответить.

Компилируемый или интерпретируемый?

Компилятор анализирует программу целиком, превращает её в машинный код и сохраняет для последующего выполнения. Интерпретатор же разбирает и выполняет программу построчно в режиме реального времени.

Технически любой язык можно как компилировать, так и интерпретировать. Но для каждого языка один из методов подходит больше, чем другой, и выбор парадигмы на ранних этапах определяет дальнейшее проектирование. В общем смысле интерпретация отличается гибкостью, а компиляция обеспечивает высокую производительность, но это лишь верхушка крайне сложной темы.

Я хотел создать простой и при этом производительный язык, каких немного, поэтому с самого начала решил сделать Pinecone компилируемым. Тем не менее, интерпретатор у Pinecone тоже есть — первое время запуск был возможен только с его помощью, позже объясню, почему.

Выбор языка

Своеобразный мета-шаг: язык программирования сам является программой, которую надо написать на каком-то языке. Я выбрал C++ из-за производительности, большого набора функциональных возможностей, и просто потому что он мне нравится.

Но в целом совет можно дать такой:

  • интерпретируемый ЯП крайне рекомендуется писать на компилируемом ЯП (C, C++, Swift). Иначе потери производительности будут расти как снежный ком, пока мета-интерпретатор интерпретирует ваш интерпретатор;
  • компилируемый ЯП можно писать на интерпретируемом ЯП (Python, JS). Возрастёт время компиляции, но не время выполнения программы.

Проектирование архитектуры

У структуры языка программирования есть несколько ступеней от исходного кода до исполняемого файла, на каждой из которых определенным образом происходит форматирование данных, а также функции для перехода между этими ступенями. Поговорим об этом подробнее.

Примечания

  1. Дорот В. Л., Новикав Ф. А. // Толковый словарь современной компьютерной лексики. — 3-е изд. — СПб.: БХВ-Петербург, 2004. — С. 215. — 608 с. — ISBN 9785941574919. — ISBN 5941574916.
  2. Макарова Н. В., Волков В. Б. // Информатика: Учебник для вузов. — СПб.: Питер, 2015. — С. 557. — 576 с. — ISBN 9785496015509.
  3. Microsoft Press. interpret, interpreted language // Толковый словарь по вычислительной технике. — М.: Русская редакция, 1995. — С. 236. — 496 с. — ISBN 5750200086. — ISBN 1556155972.
  4. ↑ I.153 interpretative language // Толковый словарь по вычислительным систамам / Под ред. В. Иллингуорта и др.. — М.: Машиностроение, 1990. — С. 241-242. — 560 с. — ISBN 521700617X.
  5. Кочергин В. И. interpreter // Большой англо-русский толковый научно-технический словарь компьютерных информационных технологий и радиоэлектроники. — 2016. — ISBN 978-5-7511-2332-1.
  6. ↑ Интерпретатор, Интерпретация // Толковый словарь по информатике / Под. ред. Г.Г. Пивняка. — Днепропетровск: Национальный горный университет, 2008. — С. 327-328. — 599 с. — ISBN 978-966-350-087-4.
  7. Воройский Ф. С. // Информатика. Энциклопедический словарь-справочник. — М.: Физматлит, 2006. — С. 325. — 768 с. — ISBN 5922107178. — ISBN 9785457966338.
  8. , 25.3. Где искать жир и патоку? Интерпретируемые языки, с. 585.
  9. , 32.4. Советы по эффективному комментированию. Производительность не является разумной причиной отказа от комментирования, с. 774.
  10. ↑ .
  11. Пратт Т., Зелковиц М. 2.1.3 Трансляторы и виртуальная архитектура // Языки программирования: разработка и реализация. — СПб.: Питер, 2002. — 688 с. — ISBN 5318001890.

Эзотерические языки

Их еще называют игрушечными — это языки, которые разработали «for fun», но вместе с тем они исследуют возможности программирования, пародируя существующие «серьезные» технологии и становясь их абсурдной реализацией.

Некоторые из них созданы на основе литературного синтаксиса, например, шекспировских пьес, кулинарных рецептов, японских стихотворений хайку. И даже на основе вымышленной инопланетной логики клингонов из сериала “Star Trek” — язык Var’Aq.

Один из широко известных эзотерических языков — Brainfuck («вынос мозга»), созданный в 1993 году Урбаном Мюллером. В нем всего восемь команд, и каждая записывается одним символом. Несмотря на свой минимализм, Brainfuck обладает тьюринг-полнотой — то есть на нем определенно можно реализовать любую вычислимую функцию. Следуя его концепции, многие разработчики написали свой игрушечный язык наподобие Brainfuck.

Трансляция байт-кода в машинный код

Трансляция байт-кода в машинный код специальным транслятором байт-кода как указано выше неотъемлемая фаза динамической компиляции. Но трансляция байт-кода применима и для простого преобразования программы на байт-коде в эквивалентную программу на машинном языке. В машинный код может транслироваться как заранее скомпилированный байт-код. Но также трансляция байт-кода в машинный код может выполняться компилятором байт-кода сразу следом за компиляцией байт-кода. Практически всегда в последнем случае трансляция байт-кода выполняется внешним транслятором, вызываемым компилятором байт-кода.

Декомпиляция

Существуют программы, которые решают обратную задачу — перевод программы с низкоуровневого языка на высокоуровневый. Этот процесс называют декомпиляцией, а такие программы — декомпиляторами. Но поскольку компиляция — это процесс с потерями, точно восстановить исходный код, скажем, на C++, в общем случае невозможно. Более эффективно декомпилируются программы в байт-кодах — например, существует довольно надёжный декомпилятор для Flash. Разновидностью декомпиляции является дизассемблирование машинного кода в код на языке ассемблера, который почти всегда благополучно выполняется (при этом сложность может представлять самомодифицирующийся код или код, в котором собственно код и данные не разделены). Связано это с тем, что между кодами машинных команд и командами ассемблера имеется практически взаимно-однозначное соответствие.

Стандартизация языков программирования[править | править вики-текст]

Типы данных

Статически типизированные языки могут быть в дальнейшем подразделены на языки с обязательной декларацией, где каждая переменная и объявление функции имеет обязательное объявление типа, и языки с выводимыми типами. Иногда динамически типизированные языки называют латентно типизированными.

Структуры данных

Основные структуры данных (списки, очереди, хеш-таблицы, двоичные деревья и пары) часто представлены особыми синтаксическими конструкциями в языках высокого уровня. Такие данные структурируются автоматически.

Семантика языков программирования


Существует несколько подходов к определению семантики языков программирования.

Наиболее широко распространены разновидности следующих трёх: операционного, деривационного (аксиоматического) и денотационного (математического).

При описании семантики в рамках операционного подхода обычно исполнение конструкций языка программирования интерпретируется с помощью некоторой воображаемой (абстрактной) ЭВМ.

Аксиоматическая (Деривационная) семантика описывает последствия выполнения конструкций языка с помощью языка логики и задания пред- и постусловий.

Денотационная семантика оперирует понятиями, типичными для математики — множества, соответствия, а также суждения, утверждения и др.

Способы реализации языков

Для любого интерпретируемого языка можно создать компилятор — например, язык Лисп, изначально интерпретируемый, может компилироваться без каких бы то ни было ограничений. Создаваемый во время исполнения программы код может так же динамически компилироваться во время исполнения.

Как правило, скомпилированные программы выполняются быстрее и не требуют для выполнения дополнительных программ, так как уже переведены на машинный язык. Вместе с тем, при каждом изменении текста программы требуется её перекомпиляция, что замедляет процесс разработки. Кроме того, скомпилированная программа может выполняться только на том же типе компьютеров и, как правило, под той же операционной системой, на которую был рассчитан компилятор. Чтобы создать исполняемый файл для машины другого типа, требуется новая компиляция.

Интерпретируемые языки обладают некоторыми специфическими дополнительными возможностями (см. выше), кроме того, программы на них можно запускать сразу же после изменения, что облегчает разработку. Программа на интерпретируемом языке может быть зачастую запущена на разных типах машин и операционных систем без дополнительных усилий.

Однако интерпретируемые программы выполняются заметно медленнее, чем компилируемые, кроме того, они не могут выполняться без программы-интерпретатора.

Языки программирования низкого уровня

Первые компьютеры приходилось программировать двоичными машинными кодами. Однако программировать таким образом — довольно трудоемкая и тяжелая задача. Для упрощения этой задачи начали появляться языки программирования низкого уровня, которые позволяли задавать машинные команды в понятном для человека виде. Для преобразования их в двоичный код были созданы специальные программы — трансляторы.

Трансляторы делятся на:

Языки низкого уровня, как правило, используют для написания небольших системных программ, драйверов устройств, модулей стыков с нестандартным оборудованием, программирование специализированных микропроцессоров, когда важнейшими требованиями являются компактность, быстродействие и возможность прямого доступа к аппаратным ресурсам.

Языки программирования высокого уровня

Особенности конкретных компьютерных архитектур в них не учитываются, поэтому созданные приложения легко переносятся с компьютера на компьютер. В большинстве случаев достаточно просто перекомпилировать программу под определенную компьютерную архитектурную и операционную систему. Разрабатывать программы на таких языках значительно проще и ошибок допускается меньше

Значительно сокращается время разработки программы, что особенно важно при работе над большими программными проектами

Сейчас в среде разработчиков считается, что языки программирования, которые имеют прямой доступ к памяти и регистров или имеют ассемблерные вставки, нужно считать языками программирования с низким уровнем абстракции. Поэтому большинство языков, считавшихся языками высокого уровня до 2000 года сейчас уже таковыми не считаются.

Адресный язык программирования

Фортран

Недостатки

Основным недостатком является более медленное выполнение программы по сравнению с выполнением программы, предварительно скомпилированной в машинный код. Например выполнение PHP и Python может оказаться в более чем 100 раз медленнее чем C++. Трансляция в байт-код и JIT-компиляция не решают этой проблемы полностью. Дополнительный слой интерпретатора или виртуальной машины замедляет выполнение программы и может требовать больше ресурсов. Во время выполнения интерпретатор всегда должен быть загружен в память (а это могут быть и большие программы, как браузер для JS или Office для VBA). Комментарии могут снижать производительность и для обхода этого создают две версии кода — готовую для использования (с удалёнными комментариями) и разрабатываемую.

В результате в среднем интерпретируемый код следует тестировать тщательнее компилируемого, строже придерживаться соглашений по оформлению программ и использовать дополнительные анализаторы качества кода. Последний недостаток выражен несильно, так как при серьезной разработке на компилируемых языках также необходимо применение этих средств.

Структура компилятора

Процесс компиляции состоит из следующих этапов:

  1. Трансляция программы — трансляция всех или только изменённых модулей исходной программы.
  2. компоновка машинно-ориентированной программы.

В первом случае компилятор представляет собой пакет программ, включающий в себя трансляторы с разных языков программирования и компоновщики. Такой компилятор может компилировать программу, разные части исходного текста которой написаны на разных языках программирования. Нередко такие компиляторы управляются встроенным интерпретатором того или иного командного языка. Яркий пример таких компиляторов — имеющийся во всех UNIX-системах (в частности в Linux) компилятор make.

Во втором случае компилятор де-факто выполняет только трансляцию и далее вызывает компоновщик как внешнюю подпрограмму, который и компонует машинно-ориентированную программу. Этот факт нередко служит поводом считать компилятор разновидностью транслятора, что естественно неверно, — все современные компиляторы такого типа поддерживают организацию импорта программой процедуры (функции) из уже оттранслированого программного модуля, написанного на другом языке программирования. Так в программу на С/С++ можно импортировать функцию написанную например Pascal или Fortran. Аналогично и напротив написанная на С/С++ функция может быть импортирована в Pascal- или Fortran-программу соответственно. Это как правило было бы невозможно без поддержки многими современными компиляторами организации обработки входных данных в процедуру (функций) в соответствии с соглашениями других языков программирования. Например современные компиляторы с языка Pascal помимо соглашения самого Pascal поддерживают организацию обработки процедурой/функцией входных в соответствии с соглашениями языка С/С++. (Чтобы на уровне машинного кода написанная на Pascal процедура/функция работала с входными параметрами в соответствии с соглашениями языка С/С++, — оператор объявления такой Pascal-процедуры/Pascal-функции должен содержать ключевое слово cdecl.) Примерами таких компиляторов являются компиляторы со всех без исключения языков программирования, используемые непосредственно.

Трансляция программы как неотъемлемая составляющая компиляции включает в себя:

  1. Лексический анализ. На этом этапе последовательность символов исходного файла преобразуется в последовательность лексем.
  2. Синтаксический (грамматический) анализ. Последовательность лексем преобразуется в дерево разбора.
  3. Семантический анализ. Дерево разбора обрабатывается с целью установления его семантики (смысла) — например, привязка идентификаторов к их декларациям, типам, проверка совместимости, определение типов выражений и т. д. Результат обычно называется «промежуточным представлением/кодом», и может быть дополненным деревом разбора, новым деревом, абстрактным набором команд или чем-то ещё, удобным для дальнейшей обработки.
  4. Оптимизация. Выполняется удаление излишних конструкций и упрощение кода с сохранением его смысла. Оптимизация может быть на разных уровнях и этапах — например, над промежуточным кодом или над конечным машинным кодом.
  5. Генерация кода. Из промежуточного представления порождается код на целевом машинно-ориентированном языке.

Недостатки[править | править код]

Основным недостатком является более медленное выполнение программы по сравнению с выполнением программы, предварительно скомпилированной в машинный код. Например выполнение PHP и Python может оказаться в более чем 100 раз медленнее чем C++. Трансляция в байт-код и JIT-компиляция не решают этой проблемы полностью. Дополнительный слой интерпретатора или виртуальной машины замедляет выполнение программы и может требовать больше ресурсов. Во время выполнения интерпретатор всегда должен быть загружен в память (а это могут быть и большие программы, как браузер для JS или Office для VBA). Комментарии могут снижать производительность и для обхода этого создают две версии кода — готовую для использования (с удалёнными комментариями) и разрабатываемую.

В результате в среднем интерпретируемый код следует тестировать тщательнее компилируемого, строже придерживаться соглашений по оформлению программ и использовать дополнительные анализаторы качества кода. Последний недостаток выражен несильно, так как при серьезной разработке на компилируемых языках также необходимо применение этих средств.

История

В ранние годы развития программирования на языки сильно влиял выбор способа выполнения. Например, компилируемые языки требовали задания типа данных переменной в момент её описания или первого использования. В то время как интерпретируемые языки в силу своей динамической природы позволяли отказаться от этого требования, что давало больше гибкости и ускоряло разработку.

Изначально интерпретируемые языки преобразовывались в машинный код построчно, то есть каждая логическая строка компилировалась непосредственно перед выполнением. В результате каждая инструкция, заключенная в тело цикла и исполняемая несколько раз, столько же раз обрабатывалась транслятором. В настоящее время такие эффекты редки. Большинство интерпретируемых языков предварительно транслируются в промежуточное представление. Оно представляет собой байт-код или шитый код (threaded code). Это набор инструкций по вызову небольших фрагментов более низкоуровневого кода, эквивалентный нескольким командам ассемблера или командам виртуальной машины соответственно. Уже этот код исполняется интерпретатором или виртуальной машиной.

Например, такую схему используют следующие языки:

  • Java
  • Python
  • Ruby (использует представление кода в виде абстрактного синтаксического дерева)

Промежуточный код может создаваться как явной процедурой компиляции всего проекта (Java), так и скрытой трансляцией каждый раз перед началом выполнения программы (Perl, Ruby) и при изменении исходного кода (Python).


С этим читают