Что такое лексемы в программировании

Пишем свой язык программирования без мам, пап и бизонов. Часть 0: теория

Тема написания своего ЯПа не дает мне покоя уже около полугода. Я не ставил перед собой цель «убить» CoffeeScript, TypeScript, ELM, тысячи их, я просто хотел понять кухню и как они вообще пишутся.

К моему неприятному удивлению, большинство из этих языков используют Jison (Bison для JavaScript), а это не совсем попадало под мою задачу — «понять», так как по сути дела Jison делает все за вас, собирает AST по заданным вами правилам (Jison как таковой отличный инструмент, который делает за вас львиную долю работы, но сейчас не о нем).

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

Стоит заметить, что данное руководство не привязано к JavaScript, он выбран исключительно из соображений скорости разработки и читаемости, так что вы можете написать свой «лисп»/»питон»/»ваш абсолютно новый синтаксис» на любом знакомом вам языке.

Также до момента написании компилятора (в нашем случае транслятора), процесс написания языка не отличается от процессов создания языков компилируемых в ASM/JVM bitcode/LLVM bitcode/etc, а это значит, что данное руководство не ограничивается созданием языка трансляцируемого в JavaScript.

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

Немного теории

Не углубляясь в википедийность, процесс трансляции исходного кода в конечный JS код протекает следующим образом:

Что тут происходит:

1) Lexer

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

Т.е. на выходе из этого (CoffeeScript):

Мы получаем это (сокращенная запись):

Так-как CoffeeScript отступо-чувствительный и не имеет явного выделения блока скобками < и >, блоки отделяются отступами ( INDENT ом и OUTDENT ом), которые по сути заменяет скобки.

2) Parser

Парсер составляет AST из токенов (лексем). Он обходит весь массив и рекурсивно подбирает подходящие паттерны, основываясь на типи токена или их последовательности.

Из полученных токенов в пункте 1, parser составит, примерно такое дерево (сокращенная запись):

Не стоит пугаться объема дерева, на деле он генерируется рекурсивно и его создание не вызывает трудностей.

3) Compiler

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

Компилятор (читай транслятор) преобразует Абстрактно-Синтаксическое Дерево в JavaScript код:

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

Habrlang

Итак, разобравшись с теорией, нам предстоит собрать свой язык программирования, у которого будет примерно следующий синтаксис (что-бы не особо париться, мы будем делать смесь из Ruby, Python и CoffeeScript):

В следующей главе вы реализуем все основные классы нашего транслятора, и научим его транслировать комментарии Habrlang‘а в JavaScript.

Источник

Что такое лексемы в программировании

Важно отметить, что препроцессор не знает синтаксиса С.

2.3.2. Пространство между лексемами

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

Например, рассмотрим две последовательности: int i; int j;
и
int i;
int j;

2.3.3. Запись длинных строк

Для записи длинных строк символов используется обратная косая черта (\). Она ставится в конце строки. Обратная косая черта и символ перевода строки игнорируются, две строки (и более) воспринимаются как одно целое. Например: “Томский государственный\ университет систем управления\ и радиоэлектроники”

Здесь записана одна длинная строка символов.

2.3.4. Комментарии

Комментарии представляют собой фрагменты текста, предназначенные для записи пояснений. Комментарии предназначены для программистов, которые будут читать исходный текст. Комментарии в процессе трансляции программы игнорируются. Комментарии можно записать двумя способами. В первом способе комментарий открывается парой символов /*, а закрывается символами */. Например: int /* объявить */ i /* счетчик */ ;

В процессе трансляции будет получено всего три лексемы: int, i, ;

Второй способ записи комментария в С++ состоит в записи двух подряд символов «косой черты» (//). Комментарий начинается от этих символов (//) и заканчивается символом перевода строки. Например: class X // это комментарий;
. ;

Следует быть осторожным в использовании /* и // одновременно. Это может привести к нежелательным последствиям. Например: int i = j //* разделить на k */k;
+m;

Источник

Лексемы

Что такое лексемы в программировании. Смотреть фото Что такое лексемы в программировании. Смотреть картинку Что такое лексемы в программировании. Картинка про Что такое лексемы в программировании. Фото Что такое лексемы в программировании Что такое лексемы в программировании. Смотреть фото Что такое лексемы в программировании. Смотреть картинку Что такое лексемы в программировании. Картинка про Что такое лексемы в программировании. Фото Что такое лексемы в программировании Что такое лексемы в программировании. Смотреть фото Что такое лексемы в программировании. Смотреть картинку Что такое лексемы в программировании. Картинка про Что такое лексемы в программировании. Фото Что такое лексемы в программировании Что такое лексемы в программировании. Смотреть фото Что такое лексемы в программировании. Смотреть картинку Что такое лексемы в программировании. Картинка про Что такое лексемы в программировании. Фото Что такое лексемы в программировании

Что такое лексемы в программировании. Смотреть фото Что такое лексемы в программировании. Смотреть картинку Что такое лексемы в программировании. Картинка про Что такое лексемы в программировании. Фото Что такое лексемы в программировании

Что такое лексемы в программировании. Смотреть фото Что такое лексемы в программировании. Смотреть картинку Что такое лексемы в программировании. Картинка про Что такое лексемы в программировании. Фото Что такое лексемы в программировании

Алфавит языка

Лекция1 Состав языка

Вводятся базовые для всего дальнейшего изложения понятия: из каких простейших «кирпичиков» состоят все тексты на языке программирования, что понимают под типом данных, какие встроенные типы данных есть в языке C++.

Все тексты на языке пишутся с помощью его алфавита. Алфавит C++ включает:

Из символов базового набора составляются лексемы языка и директивы препроцессора. Символы из набора реализации используются для написания комментариев. Компилятор комментарии игнорирует.

Существуют следующие виды лексем:

Границы лексем определяются другими лексемами, такими, как разделители или знаки операций.

Лексемы языка программирования аналогичны словам естественного языка. Например, лексемами являются константа 128 (но не ее часть 12), имя Vasia, ключевое слово goto и знак операции сложения +. Из лексем составляются выражения и операторы.

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

Объединенная единым алгоритмом совокупность описаний и операторов образует программу.

Источник

Введение в теорию компиляторов: лексический анализ языка Pascal средствами C#

Введение

В последнее время большинство новичков в программировании начинают с высокоуровневых языков, таких, как Java, Python, C#, или любой другой язык, содержащий в себе “джентльменский набор” в виде сборщика мусора, готовых структур данных и так далее. Конечно, такой подход имеет свои плюсы, но, как правило, начинающий разработчик, использующий готовый функционал языка, упускает самое главное – его устройство и механизмы работы и имплементации.

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

Лексический анализатор – первый из “слоев” компилятора, отвечающий за выделение лексем для последующей обработки.

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

Реализация

Описание структуры

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

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

Наилучшим решением для обработки лексем будет служить некий конечный автомат. Это позволит избавиться от лишних if-ов, а также даст возможность легко вносить изменения в цикл. S — начальное состояние, NUM, DLM, ASGN, ID — состояния соответствующих видов лексем, ER будет использоваться для ошибки, а FIN для конечного состояния.

Основными методами являются SearchLex, который ищет лексему в нашем массиве и возвращает ее id и значение в кортеже (да, кортежи тоже бывают полезными), а также PushLex, который добавляет новую лексему в словарь.

Реализация алгоритма

Первым делом стоит определить конец работы цикла — состояние FIN, а также реализовать начальное состояние, которое будет

Метод GetNext позволяет получить следующий символ в строке, ClearBuf, соответственно, очищает буфер, хранящий в себе лексему

Особое внимание стоит уделить оператору присваивания «:=», который состоит из двух отдельных операторов. Самым простым способом определения данного оператора является добавление условия и запись промежуточного значения в буфер. Для этого было реализовано отдельное состояние ASGN (в переводе assign — присваивание). В случае определения буфера как «:», алгоритм просто добавит новую лексему, а если следующим знаком является «=», то будет добавлен уже один оператор присваивания.

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

Тестирование

Протестировать алгоритм можно по-разному: указать напрямую путь .pas файла, программно создать строку или любой другой удобный вариант. Так как мы пишем на C#, не составит труда добавить форму в приложение, на которой будет 2 textBox-а, первый для ввода кода программы, второй — выводит результат работы алгоритма.

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

Входные данные

Выходные данные

Полный алгоритм

Заключение

Может показаться, что лексический анализатор штука не очень понятная, да и собственно не очень важная. Почему нельзя вынести все это в синтаксический анализатор? Как работать со сложными конструкциями? Да, способы реализации лексического анализатора разнятся от компилятора к компилятору, но при разборе всех этих принципов появится не только понимание работы языка программирования X, но и появится фундамент для разработки собственного языка программирования: второй Python, или язык для вашей предметной области — все это можно реализовать при понимании всех специфик работы и устройства компилятора в общем виде.

Источник

Что такое лексемы в программировании

логическое И (истинно тогда и только тогда, когда оба операнда истинны)

логическое ИЛИ 9ложно тогда и только тогда, когда оба операнда ложны)

логическое НЕ (логическое отрицание)

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

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

Комментарии начинаются либо с двух символов «прямая косая черта» (//) и заканчиваются символом перехода на новую строку, либо заключаются между символами — скобками /* */. Внутри комментария можно использовать любые символы.

Типы данных в С++

Данные различных типов хранятся и обрабатываются по-разному. Тип данных определяет:

В С++ определено 5 простых типов данных:

Перед типами char, int, double разрешается использовать модификаторы, уточняющие внутреннее представление и диапазон:

Целые типы данных

Целые типы данных используются для представления целых чисел. (Таблица 5).

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

Наиболее часто используемый целый тип int задает целые данные. Для 16-разрядного процессора под величины этого типа отводится 2 байта, а для 32-разрядного — 4 байта.

Источник

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

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