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

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

26 апреля 2022 г.

В этой части создания вашего собственного языка программирования мы улучшим наш лексический анализ с помощью Regex lookaheads и lookbehinds (пожалуйста, ознакомьтесь с [Часть I] (https://hackernoon.com/building-your-own-programming-language-from- нуля) и [Часть II] (https://hackernoon.com/building-your-own-programming-language-from-scratch-part-ii-dijkstras-two-stack-algorithm)).


В основном, просмотры вперед и назад позволяют вам создавать свои собственные подвыражения ^ и $ (якоря). С их помощью можно настроить условие, которое будет выполняться в начале и в конце строки или не будет выполняться, и это условие не будет частью «совпадающего» выражения.


Lookbehind может «заглянуть» назад и должен быть помещен в начало регулярного выражения. Lookahead «смотрит» вперед и, соответственно, должен располагаться в конце.


Существует четыре типа просмотра вперед и назад с соответствующим синтаксисом регулярных выражений:


  1. Положительный просмотр вперед: (?=шаблон)

  1. Отрицательный просмотр вперед (?!pattern)

  1. Положительный взгляд назад (?<=шаблон)

  1. Отрицательный просмотр назад (?<!pattern)

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


  1. Лексема ключевого слова if|then|end|print|input|struct|arg

Предположим, мы определили переменную, начинающуюся с ключевого слова:


```питон


печатное_значение = 5


распечатать print_value


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


```java


(if|then|end|print|input|struct|arg)(?=\s|$)


  1. Логическая лексема «истина|ложь».

То же правило применяется к лексеме «Логическая»:


истинное_значение = истина


Чтобы правильно прочитать лексему «Логическая», она должна быть разделена либо пробелом, либо концом строки:


```java


(истина|ложь)(?=\s|$)


  1. Числовая лексема [0-9]+

Для Числовых значений мы введем возможность чтения чисел с плавающей запятой с десятичной точкой и чисел с отрицательным/положительным знаком перед числом:


```питон


отрицательное_значение = -123


float_value = 123,456


leading_dot_float_value = .123


ending_dot_float_value = 123.


Во-первых, для соответствия необязательным + или - мы добавляем класс символов:


```java


[+-]?[0-9]+


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


```java


[+-]?[0-9]+[.]?[0-9]*


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


```java


[+-]?([0-9]+[.]?[0-9]*|[.][0-9]+)


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


```java


[+-]?((?=[.]?[0-9])[0-9][.]?[0-9])


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


```java


открытый класс NumericValue расширяет Value {


общественное числовое значение (двойное значение) {


супер(значение);


@Override


общедоступная строка toString () {


if ((getValue() % 1) == 0) //выводим целое число без дробей


вернуть String.valueOf(getValue().intValue());


вернуть супер.toString();


  1. Оператор ([+]|[-]|[*]|[/]|[>]|[<]|[=]{1,2}|[!]|[:]{2}|[ (]|[)]|новый)

``рубин


структура объекта


номер аргумента


конец


новое_значение = новый объект [ 5 ]


Для оператора new нам также нужно ввести положительное опережающее значение, разделяющее значение пробелом или концом строки:


```java


([+]|[-]|[*]|[/]|[>]|[<]|[=]{1,2}|[!]|[:]{2}|[(]|[ )]|новый(?=\s|$))


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



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE