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


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

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


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)

Поделиться с друзьями
-->

Комментарии (6)


  1. knekrasov
    07.06.2017 15:56
    +3

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


  1. farcaller
    07.06.2017 15:59
    +2

    Для начала — наверно неплохо. Хотя вы не разбиваете код на токены, ведь ; это, по сути, тоже токен, а printf Test — это два токена.


    На http://www.dabeaz.com/ply/example.html смотрели?


  1. token
    07.06.2017 16:02

    Крутой язык программирования у Вас получился )


  1. fireSparrow
    07.06.2017 16:17

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

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


  1. Armleo
    07.06.2017 16:40

    "У вас он не Тьюринг полный! И вообще не ТруЪ" © 2008


  1. yurij_volkov
    07.06.2017 16:48
    +3

    1. Кажется в правилах написания статей на Хабре указано, что нельзя публиковать статьи с текстом: «Я ещё только начинающий, не бейте меня сильно».
    2. Разберитесь, пожалуйста, в назначении лексера и парсера.
    3. Как было замечено выше в комменте нельзя принимать «printf Test» как один токен. Ибо если вместо строки там будет математическое выражение, то придётся его токенизировать в парсере, что принципиально неправильно.
    4. Парсер не должен на ходу исполнять код. Он на выход должен выдавать синтаксическое дерево (или другую структуру данных, которая нужная для исполнения) и передавать её соответствующей функции.
    ЗЫ: Ну и если вам действительно 11 лет, то не останавливайтесь, разберитесь лучше в теме и напишите более качественную статью.