Допустим, проектируется некое синтаксическое дерево обычных математических выражений.
На практике элементы такого дерева могут быть "слабо детализированны"
enum ExpressionKind
{
Plus, Minus,
}
abstract class Expression
{
public ExpressionKind Kind { get; private set; }
}
class UnaryExpression : Expression
{
public Expression Expression { get; private set; }
}
interface IExpressionVisitor
{
void VisitUnary(UnaryExpression value);
}
или "лучше детализированными":
abstract class UnaryExpression : Expression
{
public Expression Expression { get; private set; }
}
class PlusExpression : UnaryExpression
{
}
class MinusExpression : UnaryExpression
{
}
interface IExpressionVisitor
{
void VisitPlus(PlusExpression value);
void VisitMinus(MinusExpression value);
}
то есть во втором случае может быть отдельный класс (и отдельный метод в визиторе) для каждого вида выражения, а в первом — некоторые типы могут представлять сразу несколько видов выражений. Первый подход использован в System.Linq.Expressions, второй — в System.Data.Common.CommandTrees.
1. Из чего порекомендуете исходить при выборе между первым и вторым подходом при проектировании своего дерева?
2. Как и почему называете методы в визиторе: просто Visit(X value) или с суффиксом из части имени типа артумента VisitX(X value)?
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>1. Из чего порекомендуете исходить при выборе между первым и вторым подходом при проектировании своего дерева?
Разница в логике обработки. То есть, например, для унарных операций вообще, как и для каждой в отдельности я бы отдельный класс не создавал, но, может быть, объединил бы '+' и '-', '*' и '/'.
_FR>2. Как и почему называете методы в визиторе: просто Visit(X value) или с суффиксом из части имени типа артумента VisitX(X value)?
Здравствуйте, adontz, Вы писали:
_FR>>1. Из чего порекомендуете исходить при выборе между первым и вторым подходом при проектировании своего дерева? A>Разница в логике обработки. То есть, например, для унарных операций вообще, как и для каждой в отдельности я бы отдельный класс не создавал, но, может быть, объединил бы '+' и '-', '*' и '/'.
А какая например может быть разная логика? С точки зрения программы, ей что складывать, что умножать, что делить, что косинус считать — по барабану Единственное практическое значение имеет, кажется, просто набор данных, лписывающих операцию (один операнд, два операнда, имя функции и набор параметров). Но с этим подходом другая загвоздка: пользователю надо уже оперировать не только с типом объекта, но ещё и с дополнительным перечислителем, который уточняет назначение элемента AST. Вот вам удобнее было бы с каким АСТ работать? Почему?
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>А какая например может быть разная логика? С точки зрения программы, ей что складывать, что умножать, что делить, что косинус считать — по барабану Единственное практическое значение имеет, кажется, просто набор данных, лписывающих операцию (один операнд, два операнда, имя функции и набор параметров).
Да. На этапе создания AST безотносительно того, как предполагается его использовать, больше ничего не важно. Имя функции и набор параметров — достаточно для описания любых выражений.
Но AST часто строится под определенную грамматику, и потому, имеет в себе след этой грамматики. Именно поэтому в AST могут присутствовать Term, Factor, Expr и т.п. Чем больше в AST заложено данных грамматики, тем скорее мы не сможем создать дерево, не удовлетворяющее грамматике.
_FR>Но с этим подходом другая загвоздка: пользователю надо уже оперировать не только с типом объекта, но ещё и с дополнительным перечислителем, который уточняет назначение элемента AST. Вот вам удобнее было бы с каким АСТ работать? Почему?
Мне кажется что особой разницы здесь нет для пользователя. Либо ему придется оперировать с большим множеством типов, либо с малым + перечислителем. Если у пользователя есть паттерн-матчинг, то ему будет удобно и то и другое.
При отсутствии паттерн-матчинга, нужно будет прикручивать визитора. Но и визитор сможет щупать перечислители при необходимости. Просто это будет не совсем классический визитор и только. Кстати, визитор можно сделать абсолютно внешне-навесным на типы AST, т.е. метод Visit в AST можно не делать.
Это что касается удобства. Если вспомнить о производительности, то встроенный visitor будет эффективнее.
Здравствуйте, samius, Вы писали:
S>Но AST часто строится под определенную грамматику, и потому, имеет в себе след этой грамматики. Именно поэтому в AST могут присутствовать Term, Factor, Expr и т.п. Чем больше в AST заложено данных грамматики, тем скорее мы не сможем создать дерево, не удовлетворяющее грамматике.
Это несколько другое — тут у "Term, Factor, Expr" различная семантика. У операторов она одна и та же (тут-то сомнения и берут) и везде, где можно использовать бинарный плюс можно же использовать и умножение. Однако в одной библиотеке, я смотрю, сделано одним образом, в другой — другим. Правидльного способа, понятно, нет: но интересны предпосылки каждого из.
_FR>>Но с этим подходом другая загвоздка: пользователю надо уже оперировать не только с типом объекта, но ещё и с дополнительным перечислителем, который уточняет назначение элемента AST. Вот вам удобнее было бы с каким АСТ работать? Почему? S>Мне кажется что особой разницы здесь нет для пользователя. Либо ему придется оперировать с большим множеством типов, либо с малым + перечислителем. Если у пользователя есть паттерн-матчинг, то ему будет удобно и то и другое.
Эх, был бы ПМ я бы не сомневаясь делал бы разные типы — всё-таки when смотрится не очень, да и длиннее, учитывая, то нужно и имя перечислителя неварное указывать.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, adontz, Вы писали:
_FR>>А какая например может быть разная логика? A>Приоритеты разные, например. Это не то чтобы разные логика, скорее разныая обработка.
Приоритет — всего лишь атрибут. В любом случае мой конкретный АСТ будет наоборот пораждать текст, а не парсится из него, так что приоритетов нет.
_FR>>Вот вам удобнее было бы с каким АСТ работать? Почему? A>Сильно зависит от того, что вкладывается в понятие "работать". Надо от сценариев плясать.
А какие бывают вырианты-то Единственный сценарий — это анализ дерева да его трансформация. С помощью визитора в моей конкретной ситуации, но не думаю, что механизм разбора так уж важен.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>>>Вот вам удобнее было бы с каким АСТ работать? Почему? A>>Сильно зависит от того, что вкладывается в понятие "работать". Надо от сценариев плясать. _FR>А какие бывают вырианты-то
Ну по идее объединение одинаково обрабатываемых нескольких лексем в один тип позволит избежать копипейста. Вот собственно и всё.
Здравствуйте, _FRED_, Вы писали:
_FR>Здравствуйте, samius, Вы писали:
S>>Но AST часто строится под определенную грамматику, и потому, имеет в себе след этой грамматики. Именно поэтому в AST могут присутствовать Term, Factor, Expr и т.п. Чем больше в AST заложено данных грамматики, тем скорее мы не сможем создать дерево, не удовлетворяющее грамматике.
_FR>Это несколько другое — тут у "Term, Factor, Expr" различная семантика. У операторов она одна и та же (тут-то сомнения и берут) и везде, где можно использовать бинарный плюс можно же использовать и умножение. Однако в одной библиотеке, я смотрю, сделано одним образом, в другой — другим.
Разделение на term/factor/expr — это один из способов задания приоритетов операций. Потому с этой точки зрения + и * не вполне заменимы:
а)
Здесь '+' и '*' это атрибут выражения (перечислитель, или имя оператора)
б)
expr ::= expr + expr | term
term ::= term ∗ term | factor
factor ::= (expr ) | nat
nat ::= 0 | 1 | 2 | ···
здесь все разложено по полочкам. Но тут проблема — нельзя внести новую операцию без переколбашивания грамматики.
_FR>Правидльного способа, понятно, нет: но интересны предпосылки каждого из.
А о каком языке мы говорим, хотя бы в общих чертах? Можно ли вводить в нем операторы, указывать их приоритеты?
Здравствуйте, samius, Вы писали:
_FR>>Правидльного способа, понятно, нет: но интересны предпосылки каждого из. S>А о каком языке мы говорим, хотя бы в общих чертах? Можно ли вводить в нем операторы, указывать их приоритеты?
Языков несколько, все те или иные подмножества SQL. Парсить мне их не придётся, работа прямо противоположная. И вопрос конечно не самих по себе операторах и их приоритетах, а в том, с каким AST и почему удобнее работать.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>Здравствуйте, samius, Вы писали:
_FR>Языков несколько, все те или иные подмножества SQL. Парсить мне их не придётся, работа прямо противоположная. И вопрос конечно не самих по себе операторах и их приоритетах, а в том, с каким AST и почему удобнее работать.
Т.е. набор операторов и их приоритеты фиксированные; AST, судя по всему, является результатом некой генерации (возможно LINQ провайдер, но не важно) и предназначено для отображения в некое указанное подмножество SQL.
Верно?
А язык, на котором обрабатывается AST — C#?
В таком случае я бы для начала предпочел обобщенные Unary/Binary операторы с типом оператора указанном в перечислении. Просто на том основании что потребуется меньше типов, и как следствие, визитор будет менее размашист.
А окончательное решение принимал бы после прототипирования как минимум одного варианта.
Здравствуйте, samius, Вы писали:
S>…А язык, на котором обрабатывается AST — C#?
Всё верно.
S>В таком случае я бы для начала предпочел обобщенные Unary/Binary операторы с типом оператора указанном в перечислении. Просто на том основании что потребуется меньше типов, и как следствие, визитор будет менее размашист. S>А окончательное решение принимал бы после прототипирования как минимум одного варианта.
Да в том-то и дело, что и сами объекты и даже визитор мне изменять совсем не сложно, в любой момент могу сделать и так и эдак — неприятно менять будет только когда это уже будет использоваться
Я по большей части рассчитывал на то, что кто-то, кому приходилось и теми и другими визиторами пользоваться, сумее рассказать какой ему удобнее и, возможно, почему. Мне так абсолютно по барабану, по-умолчанию приходится считать, что чем детальнее, тем лучше
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>Я по большей части рассчитывал на то, что кто-то, кому приходилось и теми и другими визиторами пользоваться, сумее рассказать какой ему удобнее и, возможно, почему. Мне так абсолютно по барабану, по-умолчанию приходится считать, что чем детальнее, тем лучше
Я использовал свое AST (которое по сути являлось клоном System.Linq.Expressions) для транспортировки и анализа простых linq запросов (фактически только для фильтрации where) с последующей генерацией SQL. Работа на уровне BinaryOperator показалась вполне удобной.
Единственное отличие моих узлов AST было в том, что ответственность за диспетчеризацию посетителя была вынесена во внешний екстеншн метод, который использовал механизмы dynamic, ну и [Seializable].
Здравствуйте, _FRED_, Вы писали:
_FR>Да в том-то и дело, что и сами объекты и даже визитор мне изменять совсем не сложно, в любой момент могу сделать и так и эдак — неприятно менять будет только когда это уже будет использоваться
Как вариант, можно завести компромиссное решение — абстрактный UnaryOperator со свойством Kind + типизированные наследники.
_FR>Я по большей части рассчитывал на то, что кто-то, кому приходилось и теми и другими визиторами пользоваться, сумее рассказать какой ему удобнее и, возможно, почему.
Использовал вариант выше, задача — разбор/валидация путей к файлам/директорий под win и операции с ними — общий корень, сделать путь относительным и т.п. Как оказалось, код получался весьма нетривиальным, особенно при наличии relative volume token (\). Подход конечно слегка избыточен, но мне сэкономил кучу времени.