Насколько я понимаю, вы хотите проанализировать текстовое выражение, чтобы получить что-то, что вы можете оценить несколько раз.
К тому времени, когда вы получите это "что-то, что вы можете оценить", детали языка выражения Вы проанализировали, включая древовидную структуру, типы операторов и т. д. c., все должны быть учтены. Не вставляйте sh эти проблемы в код, который нужно просто оценить.
Итак, ваш процесс синтаксического анализа должен создать объект, который реализует интерфейс примерно так:
interface IExpression {
double evaluateNumeric(Environment env);
boolean evaluateLogical(Environment env);
String evaluateString(Environment env);
boolean isConstant();
}
Здесь объект Environment
содержит значения переменных, определений функций или чего-либо еще - все, что выражения могут использовать в качестве входных данных.
Как вы можете видеть, интерфейс позволяет любому выражению оцениваться как логическое значение ( если используется в if, например), как число или как строка. В зависимости от языка выражения некоторые из них могут выдавать UnsupportedOperationException
. Например, для JavaScript они почти всегда будут работать.
У вас также могут быть методы, которые сообщают вам о выражении, например isConstant
. В зависимости от языка, может быть важно предоставить тип объекта, который выражение генерирует естественным образом.
Чтобы исключить проблемы реализации операций из анализатора, вы можете предоставить ему заводской интерфейс, подобный этому:
IExpressionFactory {
IExpression add(IExpression left, IExpression right);
IExpression multiply(IExpression left, IExpression right);
///... etc. etc.
}
Парсер будет вызывать здесь метод для каждого подвыражения, чтобы создать его из своих дочерних элементов.
Различные виды подвыражений могут быть реализованы любым удобным для вас способом. В Java я обычно использую классы для каждой категории с такими лямбда-аргументами, как:
class ExpressionFactory : IExpressionFactory {
...
IExpression add(final IExpression left, final IExpression right) {
return arithmeticBinary(left, right, (a,b)->a+b);
}
...
}
Это избавляет вас от необходимости писать класс для каждого оператора. Когда я очень обеспокоен скоростью оценки, я буду использовать встроенные классы с абстрактными основами.
ВАЖНО: обратите внимание, что поскольку скомпилированный IExpression
не предоставляет дерево выражений, которое было проанализировано для его создания, вам не нужно сохранять эту структуру, и вы можете выполнять оптимизации, такие как постоянное свертывание:
IExpression arithmeticBinary(IExpression left, IExpression right, DoubleBiFunc operator) {
if (left.isConstant() && right.isConstant()) {
// The operands are constant, so we can evaluate this during compilation
double value = operator.apply(
left.evaluateNumeric(EMPTY_ENV),
right.evaluateNumeric(EMPTY_ENV));
return new NumericConstant(value);
} else {
return new ArithmeticBinaryExpression(left, right, operator);
}
}
Обрабатывая как можно больше данных во время компиляции, вы можете ускорить оценку, избавившись от всех типов / преобразований проверяет и выполняет различные оптимизации.