Comments 28
А обязательно ли нужен +=?
обязательно или нет - вы поймёте, когда начнёте разрабатывать свой язык программирования.
вот вам было бы удобно каждый раз писать выражения вида `x = x + 1`?
мне стало не удобно и я решил, что в HydraScript должны быть составные присваивания
В Lua его нет, и я каждый раз проклинаю его, когда надо инкрементнуть какой-нибудь disconnected_players_cnt.
я хз, из какого языка ты пришел в c#, но Expect("Assign") - для питонистов, ради Бога и оптимизации, используй enum
не знаю, в чём проблемы была создать новый тип икспрешена и семантику под него вместо того чтобы придумывать как одну и ту же ноду и туда и сюда поместить, конечно..
Хотелось эффективно переиспользовать существующий код
переиспользование переиспользованию рознь, как по мне
тем более если это по итогу потребовало писать лишние вещи
тут палка о двух концах. с одной стороны действительно можно закидывать проект новым кодом, но мне в одиночку тяжеловато за этим успевать следить.
с другой стороны, если нужны были лишние вещи, значит либо архитектура была неподходящая и она оказалась исправлена, либо это что называется "проба пера". то есть ранее такая задача не стояла, но в будущем она решится без лишнего кода
Если язык будет развиваться, то в нем рано или поздно появится оптимизатор.
А в оптимизаторе гораздо труднее искать замену х = x + 1 на более эффективный x += 1, в то время как уже на уровне синтаксического анализа сразу был нужный вариант.
Создать себе трудности (непонятно, зачем: +=, возможно, даже компилится в другие инструкции, а перевод + в += – это уже оптимизация) и героически преодолевать их...
Я нифига не понял две вещи:
Зачем узлам AST ссылаться на родителей?
Зачем клонировать узлы 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# знать не знаю, но я честно говоря делаю за место нод списки (все ноды в одном списке просто), а всеми областями видимости уже занимается М (или интерпретатор), ну или иногда компилятор, но я честно так не делаю.
А почему бы просто не развернуть x += y в x = x + y?
Там хоть в парсере разворачивай, хоть в лексере, короче везде можно.
та же задача, только наоборот
Типа просто делаешь так в парсере (псевдокод):
если ТокенТип == "айди":
имя = ТокенСодержимое
следующийТокен()
если токенСодержимое == "+=":
следующийТокен()
выражение = парситьВыражение()
нода = АСТнода("присваивание", имя,
АСТнода("+", имя, выражение))(айди это типа id - identifier, так лексер в основном обозначает то что это не команда, а переменная)
Нет? Попробуйте просто сделать что-то подобное, если понравиться решение.
Я горячо и полностью одобряю идею операторов для ввода и вывода! Она правильная.
Но -- >>> x -- это вывод значения х?
Однако, Как-то контринтуитивно. Синтаксис намекает на ввод )))
Почему "+=" сложнее реализовать, чем "+": опыт HydraScript