Pull to refresh

Comments 28

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

вот вам было бы удобно каждый раз писать выражения вида `x = x + 1`?

мне стало не удобно и я решил, что в HydraScript должны быть составные присваивания

Я уже писал язык программирования - это был диалект Smalltalk.

Там не было +=, и мне было удобно писать (я писал что-то вроде среды разработки на этом диалекте (только на нём не считая объекта чтобы я вообще что-то вывел на экран))

Как говорится «на вкус и цвет» )

В Lua его нет, и я каждый раз проклинаю его, когда надо инкрементнуть какой-нибудь disconnected_players_cnt.

надеюсь hydrascript вы проклинать не будете 😅

Не знаю, я такие название обычно называю как какой нибудь dplayerscnt

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

я хз, из какого языка ты пришел в c#, но Expect("Assign") - для питонистов, ради Бога и оптимизации, используй enum

Чуть позже будет статья про лексер, там тоже интересное техническое решение

не знаю, в чём проблемы была создать новый тип икспрешена и семантику под него вместо того чтобы придумывать как одну и ту же ноду и туда и сюда поместить, конечно..

Хотелось эффективно переиспользовать существующий код

переиспользование переиспользованию рознь, как по мне

тем более если это по итогу потребовало писать лишние вещи

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

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

Если язык будет развиваться, то в нем рано или поздно появится оптимизатор.
А в оптимизаторе гораздо труднее искать замену х = x + 1 на более эффективный x += 1, в то время как уже на уровне синтаксического анализа сразу был нужный вариант.

Создать себе трудности (непонятно, зачем: +=, возможно, даже компилится в другие инструкции, а перевод + в += – это уже оптимизация) и героически преодолевать их...

Я нифига не понял две вещи:

  1. Зачем узлам AST ссылаться на родителей?

  2. Зачем клонировать узлы AST? Чего там в них такого мутабельного, чего в них нужно хранить помимо, собственно, синтаксиса? Если это "class.field", то хоть в левой части присваивания, хоть в правой - какая разница?

Коротко: ссылки на родителей и клонирование — не обязательные свойства AST вообще. Они нужны из-за конкретного устройства AST в HydraScript.

1. Зачем узлам ссылка на родителя

В HydraScript Parent используется как канал контекста:

  • Через него узел получает лексическую область видимости: Scope = Parent?.Scope в AbstractSyntaxTreeNode.cs.

  • ChildOf<T>() поднимается по родителям, чтобы проверять контекст: например, что return находится внутри функции, а break — внутри цикла или if (SemanticChecker.cs).

  • Для цепочек доступа Parent дополнительно играет роль ссылки на предыдущее обращение: Prev => Parent as AccessExpression; там же хранится мутабельный Next (AccessExpression.cs).

  • Семантический анализатор и генератор кода по родителю первого DotAccess/IndexAccess находят корневой MemberExpression и его идентификатор (SemanticChecker.cs, ExpressionInstructionProvider.cs).

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

2. Зачем клонировать class.field

Синтаксис действительно одинаковый. Различаются два его вхождения в дереве:

AssignmentExpression
├── Destination: class.field        // куда записать
└── Source: BinaryExpression
    └── Left: class.field           // откуда прочитать

У первого MemberExpression.Parent должен быть AssignmentExpression, у второго — BinaryExpression. Один объект не может одновременно иметь двух родителей.

Если использовать один экземпляр, получится DAG: обход вниз найдёт узел в двух ветвях, а обратная ссылка вверх будет вести только по одной из них — в зависимости от того, какой конструктор последним перезаписал Parent. Это особенно ломает вложенные части: class, field, индекс в arr[index] тоже имеют собственных родителей.

Различие чтения и записи находится в окружающем контексте. Генератор сначала вычисляет Source как чтение, затем обходит Destination и заменяет последнюю операцию чтения на запись (ExpressionInstructionProvider.cs).

Важная поправка

Для += клонирование происходит ещё в парсере, до семантического анализа. Поэтому причина клонирования — прежде всего уникальная идентичность каждого вхождения и независимые связи. Это элемент построения структуры AST.

Для простого class.field переиспользование одного объекта могло бы нарушить инвариант дерева и сломаться на obj.arr[index].field, вызовах внутри индекса, областях видимости и последующих проходах.

Спасибо, дорогая LLM-ка. Ты не виновата, что не поняла, что мой вопрос был вопросом только по форме, а вообще это был намёк твоему хозяину, что плохо он напроектировал, и детальные подробности этого плохого проектирования не очень интересны.

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

Вот видишь, LLM-ка, ты знаешь, а он не понимает. Как вниз по дереву идти, так визиторов понапишут, а потом внутри каждого визитора обратно вверх по дереву лезут уже без визиторов, с if-ами по типу операторов.

А зачем вообще родителей.

И вообще зачем нам ноды как классы.

Я конечно C# знать не знаю, но я честно говоря делаю за место нод списки (все ноды в одном списке просто), а всеми областями видимости уже занимается М (или интерпретатор), ну или иногда компилятор, но я честно так не делаю.

Ой я под M имел ввиду VM (опечатка)

А почему бы просто не развернуть x += y в x = x + y?

Там хоть в парсере разворачивай, хоть в лексере, короче везде можно.

та же задача, только наоборот

Типа просто делаешь так в парсере (псевдокод):

если ТокенТип == "айди":
    имя = ТокенСодержимое
    следующийТокен()
    если токенСодержимое == "+=":
        следующийТокен()
        выражение = парситьВыражение()
        нода = АСТнода("присваивание", имя,
          АСТнода("+", имя, выражение))

(айди это типа id - identifier, так лексер в основном обозначает то что это не команда, а переменная)

Нет? Попробуйте просто сделать что-то подобное, если понравиться решение.

Кто эти комменты минусовал и что он принял перед тем чтобы их минусовать?

Я горячо и полностью одобряю идею операторов для ввода и вывода! Она правильная.

Но -- >>> x -- это вывод значения х?

Однако, Как-то контринтуитивно. Синтаксис намекает на ввод )))

да.

>>>x // вывод
<<<x // ввод
Sign up to leave a comment.

Articles