Как все началось
Дело было утром. Я захотел создать что-то на питоне, сложное, но посильное для меня. И тут я понял, что хочу создать язык программирования…
Как я создавал его...
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.
- Не весь код с комментариями, т.к. он ориентирован на продвинутых программистов.
- Код в спойлере, т.к. он длинный и в «главный» текст статьи не входит.
- Прошу не ругаться насчет кода, мне 11 лет.
- Это моя первая статья на Хабре и вообще.
- Код в начале был взят из одной статьи на Хабре.
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)
Комментарии (6)
farcaller
07.06.2017 15:59+2Для начала — наверно неплохо. Хотя вы не разбиваете код на токены, ведь
;
это, по сути, тоже токен, аprintf Test
— это два токена.
На http://www.dabeaz.com/ply/example.html смотрели?
fireSparrow
07.06.2017 16:17Если вам действитеьно 11 лет, то для вашего возраста это всё очень круто, вы молодец, продолжайте в том же духе.
Но на полноценную статью для Хабра это не тянет. Каждый второй программист когда-то пробовал сделать свой язык. Само по себе это занятие — полезное упражнение, но явно не материал для статьи. Точно так же, как нет смысла писать статью о, например, своей реализации пузырьковой сортировки или алгоритма обхода графа в ширину.
yurij_volkov
07.06.2017 16:48+31. Кажется в правилах написания статей на Хабре указано, что нельзя публиковать статьи с текстом: «Я ещё только начинающий, не бейте меня сильно».
2. Разберитесь, пожалуйста, в назначении лексера и парсера.
3. Как было замечено выше в комменте нельзя принимать «printf Test» как один токен. Ибо если вместо строки там будет математическое выражение, то придётся его токенизировать в парсере, что принципиально неправильно.
4. Парсер не должен на ходу исполнять код. Он на выход должен выдавать синтаксическое дерево (или другую структуру данных, которая нужная для исполнения) и передавать её соответствующей функции.
ЗЫ: Ну и если вам действительно 11 лет, то не останавливайтесь, разберитесь лучше в теме и напишите более качественную статью.
knekrasov
Я, конечно, понимаю, что, говоря о Python, часто приходится думать о CPython и наоборот, но вообще язык, и то, как он исполняется — это не одно и то же. У вас же получилась эмоциональная каша.