Pull to refresh

Comments 35

А чего не в песочнице? Он бы так еще и кармы поднабрал.
Мы давно уже собирались так опубликовать.
Какой в этом смысл? В песочнице за эту статью автор практически сразу получит инвайт, не дожидаясь +50-ти, и тут же её сам от своего имени и опубликует, за что получит заслуженные плюсы.
надо дать инвайт человеку, может еще что хорошее напишет
Поглядел одним глазом на код. Весьма неплохо. Правда местами проскакивает разный coding convention — иногда поля в классах имеют вотТакие имена, а иногда _ВотТакие.
У меня только вопрос возник. У вас есть класс CompiledExpressionItem, который то константа, то операция, то переменная. Почему не сделать его абстрактным и не унаследовать от него три отдельных класса для каждой из вышеупомянутых сущностей? ИМХО так более правильно с точки зрения ООП. Все эти три сущности, должны выполнять два действия — возвращать свое значение (константа — константу, переменная свое значение, операция или функция — результат выполнения) и свое написание. Собственно этих двух методов достаточно для базового класса.
То же касается и PreparedExpressionItem, за исключением того, что там еще есть тип Delimiter, что, как мне кажется, ошибка дизайна — уж слишком у вас compiled и prepared похожи. Есть повод подумать о рефакторинге.
А вообще красиво, продолжайте в том же духе.
Ответ от автора статьи:

Насчет выделения базового абстрактного класса — да, такой путь вполне возможен как рефакторинг, приводящий к более прозрачному дизайну. Но в конкретном случае возникает вопрос — какой интерфейс должен быть у такого класса, скажем, BaseCompiledExpressionItem? метод наподобие abstract object GetAggregatedObject() не очень подходит в силу необходимости последующего кастинга к конкретному типу (double const или string variableName). Подумаю на досуге )
Compiled и Prepared действительно похожи, но только внешне. Внутренне они представляют совершенно разные сущности, т.к. являются контейнерами для разных по смыслу объектов.
Имена с подчеркивания _ я использую очень редко, в данном случае это было сделано для того, чтобы показать, что это свойство может быть преобразовано в autoproperty. Но из соображений совместимости с .Net 2.0 оставлю пока так.
А вообще, спасибо за ценные замечания.
Это чистейшая случайность. Мы давно хотели опубликовать. Только сегодня закончили статью. А ту заметили после того как опубликовали.
[мигающий кислотными цветами баннер]
Поздравляем, вы написали миллион первый парсер выражений, нажмите сюда для получения выигрыша!
[/мигающий кислотными цветами баннер]
Ой, ностальжи… Помнится, когда надо было освоить синтаксис С#, я как раз такую же задачу реализовывал: разбор строки, проверку валидности и расчёт её. Несколько часов и полное удовлетворение от результата.
Ой, а я тоже подобную штуку написал не так давно! Во флэше на AS 3.0. И встроил в прогу, которая умеет строить графики, теперь ей школьники в контакте пользуются:)

Умеет класс-парсер чуточку побольше:
— поддерживает огромное количество функций, причем функции могут быть многих переменных, а также константы и аргументы;
— умеет проводить качественный анализ ошибки ввода, с указанием места, где была произведена ошибка и давать пояснение к ней (на разных языках);
— умеет форматировать исходную формулу (удалять пробелы, менять регистр элементов и тд);
— умеет оптимизировать участки формулы, в которых не содержатся аргументы формулы;
— каждый объект класса использует общие для всех (статические) функции, константы, но есть возможность задавать локальные функции, константы и аргументы (т.е. туда по сути можно «обучить» любым формулам). После разбора форулы (а он проходит автоматически, после смены входной строки) можно, передавая массив значений аргументов, получать результаты вычисления выражения.
В той проге, ссылку на которую я дал, не весь функционал виден, потому что я писал его на будущее себе же, а GUI состряпал на скорую руку:)

Собираюсь к своему модулю документацию написать в ближайшем будущем и сорцы выложить куда-нибудь, если кому-то интересным покажется то, о чем я сейчас написал:)
Мне приложение понравилось, и не понятно к чему тут минусы.
Статья хорошая, но однозначно надо в песочницу, зря что ли делали её?
Спасибо всем откликнувшимся и отдельно NeonXP )
// по традиции
Console.WriteLine(«Hello, habr»);
Нет, это же не компилятор в MSIL. Но эта возможность реализуема, если прикрутить генерацию IL по заданному CompiledExpression. Можно посмотреть PostSharp, там вроде бы есть такой функционал.
Через Emit можно генерить IL код в программе и тут же его использовать. То есть можно классы и методы создавать на лету.
Код написан неплохо, но дизайн кода… overdeveloped, имхо. Ничего страшного, конечно, в этом нет, первая версия — как правило, жуткий прототип-франкенштейн слепленный из того, что было.

Видео по теме: «The Clean Code Talks — Inheritance, Polymorphism, & Testing», совсем по теме начинается с 07:43.
Я старался сделать код таким, чтобы он одновременно был не слишком сложен в использовании и универсален в смысле возможности добавления новых операций. Возможно, с первого взгляда кажется сложноватым, но спасибо за отзыв, есть над чем подумать. )
Есть простой способ добавить в свою программу поддержку макросов или функций — через Microsoft.CSharp.CSharpCodeProvider. Компилирование выражения, функции или класса через этот класс занимает десяток строк, а в итоге получается готовая сборка. Плюсы очевидны: простота; надёжность компилятора (используется тот же, что и в csc.exe); результат является не интерпретируемым, а исполняемым (офигенный бонус в скорости).
А что если создать второй домен, поработать в нем и потом его выгрузить, когда он будет не нужен?
Да, конечно это можно сделать, но мне кажется, это — не очень элегантный способ для простых действий (по сути эквивалентен описанному мной примеру с Delphi). К тому же, при использовании готовых решений мы лишаемся гибкости, которую приобретаем при написании собственных небольших (но полезных) решений.
Судя по комментариям, я так понял, что выражение интерпретируется, а не компилируется. Поправьте, если ошибаюсь.
В таком случае это представлает чисто академический интерес для студентов, которые начинают делать свои парсеры. Но, если говорить о практическом использовании, то большим упущением являтся отсутствие нормального скомпилированного кода (не интерпретируемого). Тем паче, что пост был рамещен в .NET.
Я бы предложил вам продолжить эту тему и представить публике вторую версию, где выражение по настоющему компилируется. В .NET это возможно сделать и сейчас, не дожидаясь выхода 4-ого Frameworka, где компилятор можно будет удобно использовать в runtime как сервис, т.е. подобная задача там будет решаться в несколько строк. Сейчас решение подобной задачи (имеется ввиду компиляция в IL) хоть и несколько утомительна (по сравнению с тем, что будем иметь в будущем), но думаю даст ощутимый прирост в скорости, и покажет по данной теме все возможности платформы .NET.
Я бы назвал это интерпретацией предкомпилированного (если можно так выразиться) кода. Что-то вроде псевдокода, в котором роль инструкций выполняют элементы (операнды и операции) в постфиксной нотации. Интерпретацией бы это было, если бы сразу из строки переводилось в результат. Здесь же — нечто среднее.
Насчет генерации IL-кода — хорошая идея, которая уже фигурировала в комментариях, спасибо, я подумаю над этим.
Спасибо за отзыв )
Хорошая статья, сам что-то такое писал, с использованием польской натации.
В файле compiller.cs в строке 58 нужно добавить ещё одно условие, чтобы из
((itemIndex > 0) && (preparedExpression.PreparedExpressionItems[itemIndex - 1].Kind == PreparedExpressionItemKind.Delimiter) && (preparedExpression.PreparedExpressionItems[itemIndex - 1].DelimiterKind == DelimiterKind.OpeningBrace)))

* This source code was highlighted with Source Code Highlighter.

получить
((itemIndex > 0) && ((preparedExpression.PreparedExpressionItems[itemIndex - 1].Kind == PreparedExpressionItemKind.Delimiter) && (preparedExpression.PreparedExpressionItems[itemIndex - 1].DelimiterKind == DelimiterKind.OpeningBrace)) || (preparedExpression.PreparedExpressionItems[itemIndex - 1].Kind == PreparedExpressionItemKind.Signature)))

* This source code was highlighted with Source Code Highlighter.


Это позволит избежать ситуации, вида 1 < -1, при которой знак "-" ошибочно трактовался как бинарная операция.
Надо добавить не только PreparedExpressionItemKind.Signature, но и DelimiterKind.Comma, чтобы верно работала такая ситуация:
Вот описание операции:
/>

/>

Вот выражение power(5,-(-5)), на нём получаю stack disbalance, правим так:
((itemIndex > 0) && (preparedExpression.PreparedExpressionItems[itemIndex — 1].Kind == PreparedExpressionItemKind.Delimiter) && ((preparedExpression.PreparedExpressionItems[itemIndex — 1].DelimiterKind == DelimiterKind.OpeningBrace) || (preparedExpression.PreparedExpressionItems[itemIndex — 1].DelimiterKind == DelimiterKind.Comma)))
Пока проверить не могу, как совместить это с вышеописанным патчем.
Only those users with full accounts are able to leave comments. Log in, please.