Как сделать свой язык программирования – Как создать свой язык программирования? Теория и практика / itProger

Как создать свой язык программирования? Теория и практика / itProger

Создание языка программирования очень сложная задача, но выполнимая. Мы расскажем основные факторы при создании своего первого языка.

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

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

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

Изучение компьютера

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

Зачем вам новый язык программирования?

Заранее определитесь с предназначением языка. Существует 2 основных направления – универсальный инструмент или узкоспециализированное решение. 

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

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

Каких концепций будет придерживаться новый язык?

На этапе планирования следует ответить на ряд ключевых вопросов, они зададут направление развития:

  • интерпретация или компиляция? Код для компилирования будет преобразовываться в машинный, затем исполняться. При использовании интерпретатора код обрабатывается построчно. На вопрос нет правильного ответа, перед разработчиком стоит сложный выбор, в какую сторону уклон делать: функциональность, безопасность, скорость работы, удобство и т. д.;
  • типизация? Если да, то разработчику будет необходимо вручную устанавливать типы данных. В противном случае придётся описывать систему, которая будет определять типы;
  • в языке будет встроен автоматический алгоритм очистки мусора или управление отдать в руки пользователя?
  • планируемая модель языка программирования: структурное, ООП, функциональная логика. Кто знает, может вам удастся разработать что-то совсем иное;
  • как язык будет себя вести в отношении конкурентов, вставка из других языков планируется? Учитывать этот аспект важно при изначальной разработке языка;
  • планируется поддержка базового функционала языка или передать все функции на сторону фреймворков?
  • какой ожидается конечный вид архитектуры программы?

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

Придумайте синтаксис для языка

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

При этом синтаксис может быть каким вы только сами захотите. К примеру, существует язык от отечественных разработчиков, который называется YoptaScript. Он имеет очень забавный синтаксис и писать программы на нем приносит лишь смех 🙂

PS: этот язык является лишь шуткой и его не стоит воспринимать как реальный язык. Посмотреть язык вы можете на их официальном сайте.

Назовите ваше детище

Вопрос с одной стороны простой, с другой – нет. Многие разработчики не выбирают глубокое и замысловатое название, отдают предпочтение простоте и лёгкости запоминания. Особенно эффективно давать имя языку с явной ассоциацией, чтобы потенциальный пользователь запоминал название после первого-второго произнесения. Сложные аббревиатуры и названия из 3 и больше слов – сложно запоминаются и быстро теряются в памяти. Имя должно быть относительно коротким и запоминающимся.

Выберите фундамент языка

Выбор языка, который будет взят за основу – это важнейший шаг. Если знаний достаточно, можно писать на ассемблере или даже машинном коде, но в современном мире лучше присмотреться к другим высокоуровневым языкам: C, C++, Swift, Pascal и другие компилируемые варианты пригодные для интерпретируемых решений. В качестве достойных языков с интерпретаторами стоит присмотреться к: Java, JavaScript, Ruby, Python – для компиляторов. Перечисленные пары снизят потерю скорости работы.

Лексер и парсер

Лексер – это инструмент для анализа лексики, деления написанного кода на отдельные элементы, их называют токены. Далее вступает в работы парсер для синтаксического анализа, его роль – организация иерархии с учётом токенов, он восстанавливает цепь событий. В качестве графического примера рассмотрим простую схему:

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

Создание основной библиотеки

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

Создание и написание тестов

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

Выпуск языка в свет

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

itproger.com

Как программисты написали язык программирования без языка программирования?

Программисты не написали язык  программирования. Написали его люди. Первый язык программирования это ЯЗЫК (человеческий), который был снабжен обособленной функцией описания последовательности действий, которые надо было выполнить, чтобы получить РЕЗУЛЬТАТ. Программирование было не чего — либо, а действий ведомого человека. По русски: Хозяин давал указания что и как делать своим батракам/рабам. 
В последствии это переросло в управление ходом работ, под ходом работ, я подразумеваю какую-либо полезную деятельность человека — строительство, мореплавание, создание скульптур и прочее.
Как итог получался продукт: карты, здания, картины, машины.

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

Я думаю дал ответ на данный вопрос.

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

Данный вопрос такой же нелепый как если бы спросили: как дровосеки создали первый топор без топора? Да как — с помощью мозгов и острой необходимости быстро рубить деревья))

На самом деле для создания языка программирования обязательно нужен другой язык программирования, более низкого уровня. Все потому, что ЯП существует не сам по себе, а в связке со своим Транслятором (называемый также Компилятором или Интерпретатором, в зависимости от конкретной реализации). Точно также и наш с вами Естественный, Великий и Могучий не существует сам по себе, а только в связке с нами, носителями способными транслировать в него свои мысли и интерпретировать чужие. Итак Транслятор любого языка — это тоже программа, но на каком языке ее пишут? Ответ: по сути он может быть написан на любом ЯП, однако для этих целей обычно используют низкоуровневый язык, т.е. такой, каждая команда которого соответствует лишь одной-двум конкретным инструкциям процессора. Для полноты картины добавлю, что все что делает Транслятор — это читает текст программы, разбирает ее в соответствии с синтаксисом данного языка, т.е. выделяет функции, переменные, определяет порядок их обработки и переводит полученный алгоритм на язык низкого уровня либо сразу в машинный код, понятный компьютеру.

Программист Владислав Янцен рассказал журналисту the-answer.ru, как появился язык программирования:

— Если кратко, то при появлении первых интегральных микросхем язык уже был придуман и запрограммирован на них. Вообще понятие языка программирования очень поверхностное. Для процессора есть только машинный код, который содержит определенный набор команд. Команда для процессора выглядит намного проще, чем мы пишем в языках программирования. Например, процессор получает команды: возьми цифру в памяти из ячейки 1, сложи со значением из ячейки 2 и помести результат в ячейку 3. Процессор умеет только складывать, вычитать, умножать, делить, считывать, записывать. А само арифметическо-логическое устройство в процессоре вообще умеет только складывать. Все остальное достигается комбинацией этих операций. Так что написать первую программу было более чем реально даже на простых единичках и ноликах, хоть это было сложно и очень долго.

Любая программа, которая делается на компьютере, в итоге превращается в набор самых примитивных операций.

Если тривиально, то сначала вся логика работала на транзисторах, транзистор имеет два состояния — открыт и закрыт (1/0). Когда появилась техническая возможность сохранять состояния на длительное время, блоки таких «включений» стали объединять, так появились перфокарты, где дырками обозначали состояния элемента блока. Блок из набора включений (бит) называют инструкции. Инструкциям стали давать символьные имена. На этом этапе программист писал программу, давая прямые инструкции процессору. Потом стали объединять набор таких инструкций в какое-либо завершенное действие и стали называть командой.

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

www.the-answer.ru

Как делаются скриптовые языки программирования? — Toster.ru

Короткий ответ: читаем книгу дракона. Есть и более навороченная литература, но начинают все с этой книги (нам по ней читали в универе курс трансляции языков).

Длинный ответ: ваш транслятор принимает на вход последовательность символов (допустим, UTF-8 текст), «понимает» ее в соответствии со спецификацией вашего языка, и выплевывает в качестве вывода инструкции на другом языке (в виде текстового файла или файла спец. формата). Этим «друим языком» может быть язык ассемблера какой-то железной платформы (x86_64, ARM, SPARC), и результирующим файлом будет бинарник под указанную архитектуру (точнее — объектный модуль, бинарник потом будет собран линковщиком) — так компилятся, например, C/C++. «Другим языком» может быть язык виртуальной машины (LLVM/байткод Java/MSIL) — так компилятся С/C++ (если через LLVM), Java, Scala, C#, F#, VB. «Другим языком» может быть и более высокоуровневый язык — часто, чтобы не париться на начальных этапах развития языка генерацией машинного кода, делают транслятор, который генерит код на Си, и этот код на Си уже компилят известным компилятором в бинарник. Или к примеру, CoffeeScript/TypeScript транслируются в JavaScript, т.к. веб-браузеры кроме джаваскрипта исполнять пока ничего не умеют.

Конечно же, вы можете написать интерпретатор, а не компилятор — тогда ваша программа будет сразу же исполнять инструкции на вашем языке, не генерируя какой-либо выходной файл. Так поступают довольно много систем, например Node.js. Python делает также, если отключить генерацию pyc-файлов (поправьте меня, если я ошибаюсь).

На каком языке реализовать сам транслятор — не имеет особого значения. Более того, хорошим тоном считается реализовать на разрабатываемом языке компилятор этого же языка — это называется self-hosting. Обычно наличие компилятора языка на этом же языке считается первым этапом серьезного отношения к языку. Разумеется, первую версию компилятора нужно будет реализовать на уже существующем языке (или сделать bootstrapping, если уж вы совсем суровый разработчик).

Т.к. в задачах разбора входного потока (parsing) уже набито очень много шишек, и люди посвятили свои жизни и научные карьеры изучению этого вопроса, то сделано и немало инструментов для помощи в разработке компилятора. Как правило, такие инструменты дают возможность описать грамматику вашего языка на некоем специализированном синтаксисе (вроде BNF), а потом по этому описанию генерят вам код лексера и парсера на удобном для вас языке (это модули, которые выполнят первичный разбор входного потока на вашем языке на токены, и построят абстрактное синтаксическое дерево (AST)). А вы уже дописываете к ним основную часть вашего компилятора. Как пример, при написании компиляторов на языке Си часто используют flex в связке с yacc/bison. Есть более комлексные пакеты, позволяющие генерить код парсеров на различных языках — ANTLR, GOLD. А можно и самому написать лексер и парсер, особенно если вы уже сделали первую версию компилятора и переписываете его на вашем же языке).

toster.ru

❶ Как создать язык программирования 🚩 Программное обеспечение

Инструкция

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

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

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

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

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

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

www.kakprosto.ru

Как создать программу?

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

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

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

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

Например, не стоит выбирать язык программирования Ассемблер, если вы планируете создать графическое Windows-приложение (через вызовы API-функций). Язык С++ лучше не выбирать тем, кто создает сайт. На языке Basic не пишутся драйверы принтера. А HTML используется лишь для разметки документов в сети интернет.

Если вы только учитесь программированию, то вам стоит выбрать язык программирования Visual Basic. Это наиболее универсальный инструмент. К тому же изучение данного языка позволит вам усвоить основы, которые будут вам полезны и при переходе на более сложный язык программирования.

Сложности при создании программ

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

elhow.ru

Как создать язык программирования / СоХабр

Как все началось


Дело было утром. Я захотел создать что-то на питоне, сложное, но посильное для меня. И тут я понял, что хочу создать язык программирования…

Как я создавал его…


Lexer

Куда же без него! Он требуется для «разделения» всего на токены. Если объяснить зачем он тогда представим: у нас есть код (CoffeScript).

a = true

if a
    console.log('Hello, lexer')

И лексер превращает этот код в это(сокращенная запись):
[IDENTIFIER:"a"]
[ASSIGN:"="]
[BOOLEAN:"true"]
[NEWLINE:"\n"]
[NEWLINE:"\n"]
[KEYWORD:"if"]
[IDENTIFIER:"a"]
[NEWLINE:"\n"]
[INDENT:"    "]
[IDENTIFIER:"console"]
[DOT:"."]
[IDENTIFIER:"log"]
[ROUND_BRAKET_START:"("]
[STRING:"'Hello, lexer'"]
[ROUND_BRAKET_END:")"]
[NEWLINE:"\n"]
[OUTDENT:""]
[EOF:"EOF"]

Но в моем случае я делаю все проще, т.к. это будет излишком трудности, а также язык программирования у меня простой. У меня все просто:
def lexer(code):
    code = code.split(";") # Токенезация
    code = code[0:-1] # Т.к. есть баг, что последний элемент пустой
    return parse(code, number=0) # "Отсылаем" это все парсеру

И код (моего «ЯП»):
printf Test; exit;

Он превратит в читабельное (!):
["printf Test", "exit"]

Парсер


Самое сложное только начинается… Сделать токенезацию легко, а обработать это сложно. В теории мы должны проверять команду, потом ее аргументы. Кажется это легко, но нет! По началу все было примерно так:
number = 0
if code[number].startswith("printf"):
    print(code[number][7:-0]
    number += 1

Но ничего не работало, точнее не печатало текст, потом я попробовал так:
number = 0
if code[number].startswith("printf"):
    print(code[number][7:-1]
    number += 1

Но приходилось писать в конце любой символ. Потом я понял, что, если узнать длину строки и обрезать с 7-го символа по последний все должно работать.
number = 0
if code[number].startswith("printf"):
    l = len(code[number])
    print(code[number][7:l]
    number += 1

Вроде работает, но если выводить текст боле два и более раз то в начале идет лишний пробел… Но даже с помощью переменной проверяющей печатался ли раньше текст, ничего не работало правильно. После нескольких десятков минут и кружки кофе я придумал, что и как.
if code[number][7] == " ":
    l = len(code[number])
    print(code[number][8:l]
else:
    l = len(code[number])
    print(code[number][7:l]

Но все равно ничего не работало 😐 и с таким лицом я пытался что-то сделать… Целый час. И спустя около полутора часа я сделал это!

l = len(code[number]) # Получаем длину
if code[number][6] == " ": # Если 6-ой символ это пробел
    print(code[number][7:l]) # Печатаем все с 7-го символа
else: # Иначе
    print(code[number][8:l]) #

Потом полчаса шаманства и… Все работает на ура!

P.S.


  1. Не весь код с комментариями, т.к. он ориентирован на продвинутых программистов.
  2. Код в спойлере, т.к. он длинный и в «главный» текст статьи не входит.
  3. Прошу не ругаться насчет кода, мне 11 лет.
  4. Это моя первая статья на Хабре и вообще.
  5. Код в начале был взят из одной статьи на Хабре.

Код
def parse(code, number=0):
    try:
        # Print function #
        if code[number].startswith("printf") or code[number].startswith(" printf"):
            # Get len
            l = len(code[number])
            # If text starts with space
            if code[number][6] == " ":
                print(code[number][7:l])
            # Else
            else:
                print(code[number][8:l])
            number += 1
            parse(code, number)

        # Input function #
        if code[number].startswith("input") or code[number].startswith(" input"):
            # Get len
            l = len(code[number])
            # If text starts with space
            if code[number][6] == " ":
                input(code[number][7:l])
            # Else
            else:
                input(code[number][8:l])
            number += 1
            parse(code, number)

        # Exit function #
        elif code[number].startswith("exit") or code[number].startswith(" exit"):
            input("\nPress \"Enter\" to exit.")
            exit()
        else:
            cl = len(code[number])
            command = code[number]
            command = command[1:cl]
            print("\n", "=" * 10)
            print("Error!")
            print("Undefined command " + '"' + command + '"' + ".")
            print("=" * 10)
            input("Press \"Enter\" to exit...")
            exit()
    except IndexError:
        input("\n[!] Press \"Enter\" to exit.")
        exit()


def lexer(code):
    code = code.split(";")
    code = code[0:-1]
    return parse(code, number=0)


code = input()
lexer(code)

sohabr.net

Как Создать Свой Язык Программирования: Теория, Инструменты И Советы От Практика | GuardianeLinks

Рассказывает программист

На протяжении последних шести месяцев я работал над созданием языка программирования (ЯП) под названием Pinecone. Я не рискну назвать его законченным, но использовать его уже можно — он содержит для этого достаточно элементов, таких как переменные, функции и пользовательские структуры данных. Если хотите ознакомиться с ним перед прочтением, предлагаю посетить

и .

Введение

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

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

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

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

Первые шаги

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

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

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

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

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


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

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

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

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

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

Лексический анализатор / лексер


Строка исходного кода проходит через лексер и превращается в список токенов.

Первый шаг в большинстве ЯП — это

. Говоря по-простому, он представляет собой разбиение текста на токены, то есть единицы языка: переменные, названия функций (идентификаторы), операторы, числа. Таким образом, подав лексеру на вход строку с исходным кодом, мы получим на выходе список всех токенов, которые в ней содержатся.

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

Flex

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

Одним из основных таких инструментов является

— генератор лексических анализаторов. Он принимает на вход файл с описанием грамматики языка, а потом создаёт программу на C, которая в свою очередь анализирует строку и выдаёт нужный результат.

Моё решение

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

Синтаксический анализатор / парсер


Список токенов проходит через парсер и превращается в дерево.

Следующая стадия — парсер. Он преобразует исходный текст, то есть список токенов (с учётом скобок и порядка операций), в

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

Bison

На этом шаге я также думал использовать стороннюю библиотеку, рассматривая

для генерации синтаксического анализатора. Он во многом похож на Flex — пользовательский файл с синтаксическими правилами структурируется с помощью программы на языке C. Но я снова отказался от средств автоматизации.

Преимущества кастомных программ

С лексером моё решение писать и использовать свой код (длиной около 200 строк) было довольно очевидным: я люблю задачки, а эта к тому же относительно тривиальная. С парсером другая история: сейчас длина кода для него — 750 строк, и это уже третья попытка (первые две были просто ужасны).

Тем не менее, я решил делать парсер сам. Вот основные причины:


В целесообразности решения меня убедило высказывание Уолтера Брайта (создателя языка D) :


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

Абстрактный семантический граф
Переход от синтаксического дерева к семантическому графу

В этой части я реализовал структуру, по своей сути наиболее близкую к «промежуточному представлению» (intermediate representation) в LLVM. Существует небольшая, но важная разница между абстрактным синтаксическим деревом (АСД) и абстрактным семантическим графом (АСГ).

АСГ vs АСД

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

Запуск

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

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

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

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

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

LLVM

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

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

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

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

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

Заключение

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

Вот общие советы от меня (разумеется, довольно субъективные):

  • если у вас нет предпочтений и вы сомневаетесь, компилируемый или интерпретируемый писать язык, выбирайте второе. Интерпретируемые языки обычно проще проектировать, собирать и учить;
  • с лексерами и парсерами делайте, что хотите. Использование средств автоматизации зависит от вашего желания, опыта и конкретной ситуации;
  • если вы не готовы / не хотите тратить время и силы (много времени и сил) на придумывание собственной стратегии разработки ЯП, следуйте цепочке действий, описанной в этой статье. Я вложил в неё много усилий и она работает;
  • опять же, если не хватает времени / мотивации / опыта / желания или ещё чего-нибудь для написания классического ЯП, попробуйте написать эзотерический, типа . (Советуем помнить, что если язык написан развлечения ради, это не значит, что писать его — тоже сплошное развлечение. — прим. перев.)

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

О том, что ввязался в историю с созданием Pinecone, ни в коем случае не жалею — это отличный эксперимент, и он только начался.

.

www.guardianelinks.com

Оставить комментарий

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *