Комментарии 22
Что-то ваш парсер получился ничуть не простой...
char[] buffer = new char[functionEnd - functionStart - 1];
expression.CopyTo(functionStart + 1, buffer, 0, functionEnd - functionStart - 1);
string functionParametrs = new string(buffer);
Кто-нибудь, расскажите автору о существовании метода Substring!
result.RemoveRange(result.Count - 1, 1);
И о методе RemoveAt тоже нужно напомнить...
julia> str = "(2 + 2) * ((2 * 2) + ((2 * 2) * (2 * 2)))" # строка с арифметикой
"(2 + 2) * ((2 * 2) + ((2 * 2) * (2 * 2)))"
julia> Meta.parse(str)
:((2 + 2) * (2 * 2 + (2 * 2) * (2 * 2)))
julia> eval(ans)
80
julia> str2 = replace(str, '*' => "+ 2 +")
"(2 + 2) + 2 + ((2 + 2 + 2) + ((2 + 2 + 2) + 2 + (2 + 2 + 2)))"
julia> str2 |> Meta.parse |> eval # цепочка функций
26
Когда то на меня произвел большое впечатление курс по компиляторам.
Т.е. как классно и гладко распарсивается синтаксис программы.
Арифметическое выражение это явно проще чем программа.
Рекурсивное чтение будет на ура проходить.
Уверен, в инете много примеров, и если вы хотите сделать действительно красиво — попробуйте посмотреть в эту сторону.
((((((((((((((((((И скобки считать не придется...))))))))))))))))))))))
Теперь про ошибки в алгоритме.
"Естественно, если мы встретили знак "+" или "-" не после цифры, значит этот знак обозначает положительность или отрицательность числа, соответственно." — нет, не естественно. Смотрите:
sin(1) - 2
С каких пор у функции sqrt есть второй аргумент? Напоминаю, что название этой функции — аббревиатура от "square root", что переводится как "квадратный корень"
В польской нотации можно записывать не только "простые" формулы, а вообще любые, и без скобок. Так, ваш пример
sqrt(2*2; log(4;2))
может быть записан как2 2 * 4 2 log sqrt
. Отсюда вопрос: зачем вам вообще понадобилась большая часть алгоритма? Зачем все эти поиски парных скобок, нахождение параметров и прочее?
Antlr, вроде, как один из самых популярных. И nuget-пакет для него есть даже. Понятно, что любой парсер требует порог вхождения, но поверьте, что самостоятельная обработка ошибок намного сложнее самописного парсера.
Я однажды сам попытался написать простой парсер типа вашего. Больше таких глупостей не делаю. )))
Как способ посмотреть, насколько это сложное и неблагодарное занятие подходит, но для чего-то в продакшен не пойдёт. Попробуйте добавить хоть одну «переменную» и все придётся переписать.
Почитайте что-нибудь по грамматикам.
Вы допустили несколько ошибок — методологических в основном.
1. Любой парсер начинается с юнит-тестов! Для начала хотя бы пары выражений, но в идеале — чем больше, тем лучше. Потратьте полчаса времени придумывая самые извратные варианты — потом это окупится с лихвой.
2. За юнит-тестами идет грамматика. Честно говоря, я был удивлен, не увидев её в статье — может, вам её уже дали в условиях задания? Если нет — обязательно нужно описать грамматику и отладить её на юнит-тестах — в инете есть для этого средства, ну или написать свои.
3. Далее — лексер. Он разбивает выражение на токены, затем парсер работает уже с ними. Одновременно токенизировать и парсить как минимум неудобно.
4. Наконец — сам парсер. Изобретать велосипед — дело, конечно, интересное, но в вопросе парсинга математических выражений — мягко говоря, неблагодарное. Все уже было украдено до нас. Задолго. Просто реализуйте алгоритм рекурсивного спуска или сортировочной станции и радуйтесь результату.
Compute
класса DataTable
static Double Eval(String expression)
{
System.Data.DataTable table = new System.Data.DataTable();
return Convert.ToDouble(table.Compute(expression, String.Empty));
}
...
Double result = Eval("(2 + 2) * ((2 * 2) + ((2 * 2) * (2 * 2)))");
...
Простой парсер арифметических операций