Создание языка программирования. часть 1

от автора

Дисклеймер?

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

Введение

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

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

И так, сначала просто скажу, что наш язык будет компилируемым. Но что это значит? Это значит, что прежде чем быть запущенной, программа на таком языке должна быть превращена в машинный код (приложение, если под ОС). Например, mingw по умолчанию превратит сишный файл в exe, а gcc — в elf, bin и прочую лабуду, там свобода в настройках гораздо больше. Так же, мы сделаем бутстрапинг компилятора — то есть, перепишем его на самом себе. Это очень частая (fasm, gcc), и очень практичная тактика. И ещё немного терминов: лексер — компонент языка, отвечающий за разделение ключевых слов и символов друг от друга. такие разделенные кусочки кода — токены — потом используются парсером — компонентом языка, который строит из линейного списка АСД — абстрактное синтаксическое дерево. АСД в свою очередь использует транспилятор(олдскуллы не бейте), переводя его в код на другом языке(чаще всего С или асм), или же в машинные коды напрямую. Думаю, теперь можно по-настоящему начинать.

Каркас программы

Не думаю, что это кому-то интересно разжевывать и/или читать, так что просто дам листинг.

листинг 0 — каркас
# импорты import os from sys import path # это нужно из-за особенностей ФС проекта path+=['.\\src', '.\\..']  # печатаем на экран "эмблемку". MS win можете поменять на свое print(f""":::    ::: ::::    ::: :+:    :+: :+:+:   :+: +:+    +:+ :+:+:+  +:+ +#++:++#++ +#+ +:+ +#+ +#+    +#+ +#+  +#+#+# #+#    #+# #+#   #+#+# ###    ### ###    ####  pHoney ver 1.9.8a for Microsoft Windows\n""")  # удаляем это потому, что этот код могут запустить # из визуалки или на ПК, где файлы *.ру не сопоставлены с питоном if "python" in sys.argv:sys.argv.remove("python")  # вычисляем путь к файлу вывода outpath = os.path.splitext(os.path.normpath(os.path.abspath(parse_path_args(sys.argv)[0])))[0]+".exe"  # меняем этот путь, если юзер скажет for i in range(len(sys.argv)):     if sys.argv[i] == '-o':         outpath = sys.argv[i + 1]         break     pass  # здесь проверяем, можем ли мы получить к входным данным доступ if len(sys.argv) < 2:     print('Error: file expected')     raise SystemExit try: # вот-так. криво, но я делал на скорую руку.     with open(os.path.normpath(os.path.abspath(parse_path_args(sys.argv)[0])), 'rt', encoding="utf-8") as _:pass     pass except FileNotFoundError:     print(prf + 'Error: file not found' + psf)     raise SystemExit except PermissionError:     print(prf + 'Error: permission denied' + psf)     raise SystemExit except KeyboardInterrupt:raise SystemExit except SystemExit as E:raise SystemExit except BaseException as E:     print(f"Error: internal error. additional info: {E}")     raise SystemExit  # открываем собственно файл, поданный на вход with open(os.path.normpath(os.path.abspath(parse_path_args(sys.argv)[0])), 'rt', encoding="utf-8") as f:lines = f.read() print(len(lines), 'bytes source, ', end='') print(len(lines.splitlines()), 'lines.\n')  # функция, которая пригодится, но позже. def fold(data: str):     return data.replace('\\t', '    ').\   replace('\\b', ' <backspace> ').\   replace('\\r', ' <carriage-return> ').\   replace(' ', ' ').\   replace('\\n', ' <new-line> ')

Лексер

Сейчас напишем лексер. Использовать будем регулярные выражения,это уже почти традиция для DIY-языков. Нашему лексеру понадобится список regex’ов и сопоставненных типов токенов.

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

листинг 1 — лексер
# импорты import re import sys  # класс для токенов class LexToken(object):   # нужны: тип, значение и позиция     def __init__(self, typ, val, pos):         self.typ = typ         self.val = val         self.ps = pos         return     def __str__(self):         return\       '{\n\ttype: '+self.typ+',\n\tvalue: \"'+\       self.val+'\",\n\tpos: '+str(self.ps)+'\n}'     def __repr__(self):         return str('LexToken{'+self.type()+\         ':\"'+self.value()+'\":'+\         str(self.ps)+'}').replace('\n', '\\n')     def __getitem__(self, i):         return [self.type, self.val][i]     def __setitem__(self, i, value):         if i == 0:             self.typ = value             pass         elif i == 1:             self.val = value             pass         else:             raise IndexError('Sequence index out of range: ' + str(i))     # возвращает тип     def type(self):         return self.typ     # возвращает значение     def value(self):         return self.val     # возвращает позицию     def pos(self):         return self.ps     pass  # ошибка для лексера, хранит всю важную инфу class LexError(BaseException):     def __init__(self, token, pos, typ='unexpected'):         self.token = token         self.pos = pos         self.type = typ         pass     pass  # собственно, лексер class Lex(object):   # опять-таки, ему нужны правила     def __init__(s, rules):         s.pos = None         s.buf = None         idx = 1         regex_parts = []         s.group_type = {}         for typ, regex in rules:             groupname = 'TOKEN%s' % idx             regex_parts.append('(?P<%s>%s)' % (groupname, regex))             s.group_type[groupname] = typ             idx += 1         s.regex = re.compile('|'.join(regex_parts))     # а ещё нужна строка, которую надо разбирать     def input(s, buf):         s.buf = buf         s.pos = 0     # генерирует 1 отдельный токен     def token(s):         if s.pos >= len(s.buf):             return None         else:             mtch = s.regex.match(s.buf, s.pos)             if mtch:                 groupname = mtch.lastgroup                 tok_type = s.group_type[groupname]                 tok = LexToken(tok_type, mtch.group(groupname), s.pos)                 s.pos = mtch.end()                 return tok             raise LexError(LexToken('<unexpected>',\             s.buf[s.pos], s.pos), s.pos)     # генератор последовательности токенов     def tokens(s):         while 1:             tok = s.token()             if tok is None: break             yield tok         return -1     pass 

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

А вот для него правила(ws я решил не опускать, на всякий случай):

RULES = [     ['str', r'(\'[.^\']*\'|\"[.^\"]*\")'],     ['comment', r'//.*|/\*.*/'],     ['name', r'[A-Za-zА-Яа-яЁё_]\w*'],     ['int', r'[+-]?(0[Xx][A-Za-z0-9][A-Za-z0-9_]*|\d+|1[01]*[Bb]|0[Bb])'],     ['float', r'[+-]?\d*\.\d+'],     ['plus', r'\+'],     ['minus', r'\-'],     ['star', r'\*'],     ['slash', r'/'],     ['back', r'\\'],     ['colon', r'\:'],     ['semi', r'\;'],     ['dot', r'\.'],     ['comma', r'\,'],     ['amper', r'\&'],     ['sharp', r'\#'],     ['expl', r'\!'],     ['dog', r'\@'],     ['bux', r'\$'],     ['percent', r'\%'],     ['flex', r'\^'],     ['lpar', r'\('],     ['rpar', r'\)'],     ['lblc', r'\['],     ['rblc', r'\]'],     ['lfig', r'\{'],     ['rfig', r'\}'],     ['lang', r'\<'],     ['rang', r'\>'],     ['equ', r'='],     ['apos', r'\`'],     ['tilde', r'\~'],     ['ignore', ' |\r|\f|\t'],     ['newline', '\n'] ]

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

Ссылки:

Теория:

  1. Компилируемые языки это

  2. Машинный код

  3. Бутстраппинг компилятора

  4. Лексер

  5. Tокен

  6. Парсер

  7. АСД(AST)

  8. Транспилятор(транслятор)

Проект:

  1. Гитхаб немного устарел. назначение и синтаксис поменялись. поэтому пока что не советую туда заглядывать. разве что звездануть.

Жажду идей, предложений и конструктивной критики.

Спасибо за прочтение!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Да или нет?
22.22% Да 4
16.67% Не знаю 3
61.11% Нет 11
Проголосовали 18 пользователей. Воздержались 2 пользователя.

ссылка на оригинал статьи https://habr.com/ru/post/677378/


Комментарии

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

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