Да. Спустя наверно 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.

Удачи!

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


  1. vadimr
    13.07.2025 09:39

    and и or – это специальные формы, а не функции! Это принципиально, и на этом в Лиспе много построено. Второй аргумент не вычисляется, если первого достаточно.

    Попробуйте у себя и в clisp:

    (and nil (princ "Hello"))
    (or t (princ "Hello"))

    Специальные формы реализуются макросами.


    1. SystemSoft Автор
      13.07.2025 09:39

      А макросов пока нет.


      1. vadimr
        13.07.2025 09:39

        Ну тогда пишите логику в парсере.

        В Лиспеor и and ничем не отличаются от if (собственно, or – это сокращённая форма cond без лишних скобок).


        1. SystemSoft Автор
          13.07.2025 09:39

          Ну, информация полезная для меня, но у меня пока or, and это обычные логические операции.


  1. vadimr
    13.07.2025 09:39

    quote и ' имеют только один аргумент. Это связано с тем, что

    ('hello world!)

    – это форма из литерала 'hello и значения атома world!. Иначе вы так всю программу заквотите одной кавычкой.


    1. SystemSoft Автор
      13.07.2025 09:39

      Ну... За квотировать всю программу не получится ведь ('hello world!) отделен от всего.


      1. vadimr
        13.07.2025 09:39

        Ну вы ж кавычку не обязательно используете на самом нижнем уровне вложенности, как здесь.


        1. SystemSoft Автор
          13.07.2025 09:39

          По другому не выполнится.