Рубрика «интерпретатор» - 3

Генерация кода во время исполнения или «Пишем свой JIT-компилятор» - 1
Современные компиляторы очень хорошо умеют оптимизировать код. Они удаляют никогда не выполняющиеся условные переходы, вычисляют константные выражения, избавляются от бессмысленных арифметических действий (умножение на 1, сложение с 0). Они оперируют данными, известными на момент компиляции.
В момент выполнения информации об обрабатываемых данных гораздо больше. На её основании можно выполнить дополнительные оптимизации и ускорить работу программы.
Оптимизированный для частного случая алгоритм всегда работает быстрее универсального (по крайней мере, не медленнее).
Что если для каждого набора входных данных генерировать оптимальный для обработки этих данных алгоритм?
Очевидно, часть времени выполнения уйдёт на оптимизацию, но если оптимизированный код выполняется часто, затраты окупятся с лихвой.
Как же технически это сделать? Довольно просто — в программу включается мини-компилятор, генерирующий необходимый код. Идея не нова, технология называется “компиляция времени исполнения” или JIT-компиляция. Ключевую роль JIT-компиляция играет в виртуальных машинах и интерпретаторах языков программирования. Часто используемые участки кода (или байт-кода) преобразуются в машинные команды, что позволяет сильно повысить производительность.
Java, Python, C#, JavaScript, Flash ActionScript — неполный (совсем неполный) список языков, в которых это используется. Я предлагаю решить конкретную задачу с использованием этой технологии и посмотреть, что получится.
Читать полностью »

Содержание

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

Хэл Абельсон и Жеральд Сасман, «Структура и интерпретация компьютерных программ».

Когда учение спросил учителя о природе цикла Данных и Контроля, Юань-Ма ответил: «Подумай о компиляторе, компилирующем самого себя».

Мастер Юань-Ма, «Книга программирования»

Создать свой язык программирования удивительно легко (пока вы не ставите запредельных целей) и довольно поучительно.

Главное, что я хочу продемонстрировать в этой главе – в построении языка нет никакой магии. Мне часто казалось, что некоторые человеческие изобретения настолько сложны и заумны, что мне их никогда не понять. Однако после небольшого самообразования и ковыряния такие штуки часто оказываются довольно обыденными.

Мы построим язык программирования Egg (Яйцо). Он будет небольшим, простым, но достаточно мощным для выражения любых расчётов. Он также будет осуществлять простые абстракции, основанные на функциях.
Читать полностью »

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

Вычислитель

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

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

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

  • Если на входе пустой список, возвращаем 0.
  • Если на входе список с одним числом, возвращаем это число.
  • Если на входе [1 2 +], возвращаем 3.

Создадим новый тестовый класс и добавим первый тест.

TEST_CLASS(EvaluatorTests) {
public:
    TEST_METHOD(Should_return_zero_when_evaluate_empty_list) {
        double result = Evaluator::Evaluate({});
        Assert::AreEqual(0.0, result);
    }
};

Читать полностью »

В первой части был написан лексер. Далее будет рассмотрена разработка парсера.

Парсер

Парсер будет реализован по алгоритму сортировочной станции, так как он достаточно прост и не нуждается в рекурсии. Сам алгоритм таков:

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

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

После достижения конца входного потока, вытолкнуть все оставшиеся в стеке операторы в выходной поток. Если в нём найдено что-либо кроме операторов, то генерируем ошибку.

Прикинем, какие тесты могут понадобиться для начала.

  • При получении пустого списка, возвращается пустой список.
  • При получении списка с одним числом, возвращается список с этим числом.
  • При получении [1 + 2], возвращается [1 2 +].
  • При получении [1 + 2 + 3], возвращается [1 2 + 3 +], так как оператор + является лево ассоциативным.
  • При получении [1 * 2 + 3], возвращается [1 2 * 3 +].
  • При получении [1 + 2 * 3], возвращается [1 2 3 * +], так как оператор * имеет больший приоритет.

Со скобками и ошибками разберёмся позднее. Итак, напишем первый тест из списка.

TEST_CLASS(ParserTests) {
public:
    TEST_METHOD(Should_return_empty_list_when_put_empty_list) {
        Tokens tokens = Parser::Parse({});
        Assert::IsTrue(tokens.empty());
    }
};

Читать полностью »

Введение

Пишем простой интерпретатор на C++ с помощью TDD, часть 1
Многие C++ программисты слышали про разработку через тестирование. Но почти все материалы по данной теме касаются более высокоуровневых языков и сосредоточены больше на общей теории, чем на практике. Итак, в данной статье я попробую привести пример пошаговой разработки через тестирование небольшого проекта на C++. А именно, как можно предположить из названия, простого интерпретатора математических выражений. Такой проект также является неплохой code kata, так как на его выполнение затрачивается не более часа (если не писать параллельно статью об этом).

Архитектура

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

Для начала, составим список того, что должен уметь наш простой интерпретатор, в порядке убывания приоритета:

  • Вычислять значение математического выражения, состоящего из чисел с плавающий точкой и математических операторов (-+/*).
  • Учёт приоритета операторов.
  • Учёт скобок.
  • Унарные плюс и минус.
  • Вычисление нескольких выражений, разделённых точкой с запятой (;).
  • Встроенные константы (pi, e).
  • Создание собственных констант с помощью оператора присваивания (=).
  • Встроенные функции с переменным числом аргументов.
  • Задание новых функций.

В данной статье будет реализация только первых трёх пунктов. Сам проект концептуально будет состоять из четырёх частей:

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

Инструментарий

Программа будет писаться в Visual Studio 2013 с установленным Visual C++ Compiler Nov 2013 CTP. Тесты будут на основе встроенного в студию тестового фреймворка для C++ проектов CppUnitTestFramework. Он предоставляет минимальную поддержку для написания модульных тестов (по сравнению с Boost.Test, или CppUTest), но, с другой стороны, хорошо интегрирован в среду разработки.

Итак, создадим новый проект типа «Native Unit Test Project» и удостоверимся, что всё компилируется.
Читать полностью »

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

Автовход с паролем и управление по ssh «в гостях» при помощи expect

Картинок на эту тему особо нет, а в статье и вообще не будет, поэтому привлечём ваше внимание обложкой замечательной книги

Читать полностью »

Простой интерпретатор с нуля на Python #4

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

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

Простой интерпретатор с нуля на Python (часть 3)

Содержание

Простой интерпретатор с нуля на Python #1
Простой интерпретатор с нуля на Python #2
Простой интерпретатор с нуля на Python #3
Простой интерпретатор с нуля на Python #4

Разъяснение к публикации

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

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

Читать полностью »

Простой интерпретатор с нуля на Python #2

Содержание

Простой интерпретатор с нуля на Python #1
Простой интерпретатор с нуля на Python #2
Простой интерпретатор с нуля на Python #3
Простой интерпретатор с нуля на Python #4

В предыдущей статье мы рассматривали сам язык IMP и основную структуру интерпретатора. Также, мы тщательно рассмотрели лексер. В этой статье мы будем писать небольшой парсер для нашего языка. Он будет извлекать AST (abstract syntax tree) из списка токенов, сгенерированных лексером. Библиотека комбинатора будет независимая, то есть с помощью нее можно будет написать парсер для любого языка.

Что такое комбинаторы парсеров?

Есть очень много способов написать парсер. Самым простым и быстрым способом сделать это являются комбинаторы.

Вы можете считать парсер функцией, которая принимает поток токенов. Если успешно, то парсер будет «съедать» немного токенов из потока. Функция вернет часть финального AST вместе с остальными токенами. Комбинатор — это функция, которая производит парсер, как его результат, обычно после приема одного или нескольких анализаторов (парсеров) в качестве входных данных, отсюда и название — «комбинатор». Вы можете использовать комбинаторы для создания законченного парсера для языка, как IMP, путем создания множества маленьких парсеров для каждой части языка.
Читать полностью »

image

От переводчика

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

Данная серия статей рассчитана на тех, кто умеет писать на python в целом, но плохо представляет как этот язык устроен изнутри. Собственно, как и я три месяца назад.

Небольшой дисклеймер: свой рассказ я буду вести на примере интерпретатора python 2.7. Всё, о чем пойдёт речь далее, можно повторить и на python 3.x с поправкой на некоторые различия в синтаксисе и именование некоторых функций.

Итак, начнём.
Читать полностью »


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js