Продолжаем делать реализацию LISP на Python. Часть 1: структуры

от автора

Да. Спустя наверно 3 дня я решил сделать это. Долго конечно, но что тут поделаешь.

Новый синтатикс: изучаем

До этого мы могли писать простые факториалы и Фибоначчи.

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

Так что вот новый синтатикс:

  • and

  • or

  • not

  • struct

and, or, not — ну… Ловите примеры:

;nil потому что 3 == 3 но 3 != 2 (and (= 3 3) (= 3 2)) ;t потому что 3 != 2 (not (= 3 2)) ;t потому что 3 == 3 (or (= 3 3) (= 3 2))

Как видите — or если одно из выражение = t, and если все два выражения = t, not если выражение = nil.

struct — другая тема.

Возможно вы знаете классы в Python, структуры в C, C++ (наверно) и других языках программирования.

Вот пример кода для структуры на C:

struct point {   int x;   int y; }

Конечно это не все прелести структур но такой код будет выглядит так на моём LISP:

(struct point (x y))

Разбираем этот код:

  • Сначала пишем имя (логично)

  • Потом элементы структуры

У меня есть (возможно) неверное мнение что структуры в C это как массивы с динамической типизации и именованием элементов.

Но не об этом.

Продолжим!

Реализуем and, or, not

Я не буду писать код в функции eval.

А добавлю and, or, not как функции.

Вот новое env:

genv.env = {     'princ': Proc(lambda *x: print(*x), 'builtins'),     'list': Proc(lambda *x: [*x], 'builtins'),     'nth': Proc(lambda lst, ind: lst[ind], ['lst', 'ind']),     '+': Proc(lambda first, sec: first + sec, ['first', 'sec']),     '-': Proc(lambda first, sec: first - sec, ['first', 'sec']),     '*': Proc(lambda first, sec: first * sec, ['first', 'sec']),     '/': Proc(lambda first, sec: first / sec, ['first', 'sec']),     '>': Proc(lambda first, sec: first > sec, ['first', 'sec']),     '<': Proc(lambda first, sec: first < sec, ['first', 'sec']),     '=': Proc(lambda first, sec: first == sec, ['first', 'sec']),     '/=': Proc(lambda first, sec: first != sec, ['first', 'sec']),     'py': Proc(lambda exp: old_eval(exp, globals() | genv.env), ['exp']),     'getk': Proc(lambda dct, name: dct[name], ['dct', 'name']),     'append': Proc(lambda lst, el: lst + [el], ['lst', 'el']),     'remove': Proc(lambda lst, el: rem(lst, el), ['lst', 'el']),     'car': Proc(lambda lst: lst[0], ['lst']),     'cdr': Proc(lambda lst: rem(lst, lst[0]), ['lst']),     'cons': Proc(lambda el, lst: [el] + lst, ['el', 'lst']),     'and': Proc(lambda f, s: f and s, ['f', 's']),     'or': Proc(lambda f, s: f or s, ['f', 's']),     'not': Proc(lambda expr: not expr, ['expr']) }

Обратите внимание на эти строки:

    'and': Proc(lambda f, s: f and s, ['f', 's']),     'or': Proc(lambda f, s: f or s, ['f', 's']),     'not': Proc(lambda expr: not expr, ['expr'])

Именно это функции and, or, not.

В них параметры:

  • or — f и s (также как первое выражение и второе выражение)

  • and — f и s (также как первое выражение и второе выражение)

  • not — expr (выражение)

Логично.

Также используем на это всё обычные питоньи операции.

А точнее — and, or, not. Логично.

Реализуем struct

Тут конечно же чуть-чуть посложнее. Но мы справимся!

Для начало давайте с того, как я реализовал концепцию структур:

  • Определяет определение структуры (и так понятно).

  • Получает… Ну, элементы структуры.

  • Когда программа видит использование структуры то имя на которое надо регистрировать элементы структуры хранится в ключевом параметре «nam» (получше не придумал) у которого обычное значение = «nil».

  • Получает значения на элементы структур и создаёт переменные типа имя.элемент_структуры с этими значениями.

К примеру мы создали структуру point с элементами x y.

Потом на переменную my_point зададим значение (point 5 6).

И пишем для прибавление x и y (+ my_point.x my_point.y).

Вот новая функция eval:

def eval(ast, nam='nil'):     try:         name = ast[0] if type(ast) != str else ast     except Exception as err:         return ast     if name == 'if':         return eval(ast[2] if eval(ast[1]) else eval(ast[3]))     elif name == 'def':         name = ast[1]         if type(name) == list and len(name) == 2:             args = name[1]             if type(args) != list:                 args = [args]             name = name[0]             procedure = Proc(lambda *args: eval(ast[2]), args)             genv.add(name, procedure)             return None         val = eval(ast[2], nam=name)         genv.add(name, val)     elif name[0] == "'":         res = ast[0].replace("'", '')         for i in ast[1:]:             res += to_lisp(i) + ' '         return res[0:-1]     elif name == 'lambda':         args = ast[1]         if type(args) != list:             args = [args]         return Proc(lambda *args: eval(ast[2]), args)     elif name == 'quote':         res = ''         for i in ast[1:]:             res += to_lisp(i) + ' '         return res[0:-1]     elif name == 'begin':         res = 'NIL'         for i in ast[1:]:             res = eval(i)         return res     elif name == 'struct':         name = ast[1]         struct = ast[2]         if type(struct) != list:             struct = list(struct)         genv.add(name, Structure(struct))     elif name == 'dict':         res = {}         for i in ast[1:]:             name = i[0]             val = eval(i[1])             res[name] = val         return res     elif name == 'nil':         return False     elif name == 't':         return True     elif name == ';':         pass     else:         fn = genv.get(name)         if type(fn) != Proc and not type(fn) == Structure:             return fn         if type(fn) == Structure:             args = [eval(i) for i in ast[1:]]             cur = 0             for i in fn.args:                 eval(parse(lexer(f'(def {nam}.{i} {arg_to_lisp(args[cur])})')))                 cur += 1             return fn         args = [eval(i) for i in ast[1:]]         if fn.args == 'builtins':             return fn.lambda_(*args)         cur = 0         for i in fn.args:             eval(parse(lexer(f'(def {i} {arg_to_lisp(args[cur])})')))             cur += 1         return fn.lambda_(*args)

Теперь про непонятное:

  1. Что за класс Structure? Ответ: дальше реализуем

  2. Что за функция arg_to_lisp? Она нужна была чтобы решить проблему с тем что когда к примеру передаёшь аргумент (list 1 2 3) то он некорректно работал. Дальше реализуем.

Теперь решаем:

Реализация класса Structure:

class Structure:     def __init__(self, args):         self.args = args #элементы структуры      def __repr__(self):         return f'#<struct {self.args}>'

Ну… ОООЧЕНЬ легко.

Реализация функции arg_to_lisp:

def arg_to_lisp(val):     if isinstance(val, list):         cur = 0         for i in val:             val[cur] = str(arg_to_lisp(i))             cur += 1         return '(list' + ' '.join(val) + ')'     elif isinstance(val, bool) or val == None:         if val == True:             return 't'         return 'nil'     elif isinstance(val, dict):         cur = 0         res = '(dict '         for i in val.values():             val[list(val.keys())[cur]] = str(arg_to_lisp(i))             res += '(' + list(val.keys())[cur] + ' ' + str(arg_to_lisp(i)) + ') '             cur += 1         res = res[0:-1]         print(res + ')')         return res + ')'     return val

Тоже легко. Чуть-чуть переделанная функция to_lisp где просто для list и dict ставится (list элементы).

Конечно затруднения есть но про словарь не думайте.

А что по поводу синтатического сахара? Если изучить код то его можно заметить:

К примеру:

Чтобы не писать (def x (lambda r (* r r))) теперь можно делать так:

(def (square x) (* x x))

Это я взял со scheme.

И также с quote:

Раньше: (quote hello world!).

Теперь: (‘hello world!)

Всё.

Итоги

В этой статье мы не только добавили struct и and, or, not. Но ещё и добавили синтатический сахар вместе с багфиксами.

Теперь можно поменять MyLISP 1.0 на MyLISP 1.5.

Удачи!


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


Комментарии

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

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