Использование шаблона интерпретатора на композитной структуре
Меня попросили сделать оценщика выражений, используя Composite, Recursive Descendent Parser и Interpreter.
Здесь грамматика:
<cond> → <termb> [OR <termb>]*
<termb>→<factb>[AND <factb>]*
<factb>→<expr> RELOP <expr> | NOT <factb> | OPAR <cond> CPAR
<expr> → [PLUS | MINUS] <term> [(PLUS <term>) | (MINUS <term>)]*
<term> → <termp> [(MULT <termp>) | (DIV <termp>) | (REM <termp>)]*
<termp> → <fact> [POWER <fact>]*
<fact> → ID | NUM | OPAR1 <expr> CPAR1
----TERMINALS----
ID → ("A" | ... | "Z" | "a" | ...| "z") [("A"| ... | "Z" | "a" | ...| "z" | "0" | ... | "9")]*
NUM → ("0" | ... | "9") [("0" | ... | "9")]*
OPAR → "("
CPAR → ")"
OPAR1 → "["
CPAR1 → "]"
RELOP → EQ | NEQ | GT | GE | LT | LE
EQ → "= ="
NEQ → "!="
GT → ">"
GE → ">="
LT → "<"
LE → "<="
POWER → "^"
DIV → "/"
REM → "%"
MULT → "*"
MINUS → "−"
PLUS → "+"
AND → "and" or "&&"
OR → "or" or "||"
NOT → "not" or "!"
Назначение:
Цель проекта, основанная на Composite, Recursive Builder и Интерпретатор, должен получить условное выражение, выполнить синтаксический анализ и построить его составное дерево. Начиная с дерева, вы должны оценивать результат условия на основе внешнего контекста (чтение из файла свойств), который содержит значение внутреннего Переменные
Теперь первое, что я заметил, это то, что Interpreter использует композитную структуру, поэтому было бы неплохо расширить мою структуру Composite с помощью метода оценки (: Контекст).
Я спросил, но мне сказали, что это не способ выполнить задание.
Похоже, что у меня построено дерево интерпретаторов, начиная с Composite (что для меня совершенно бессмысленно, поскольку у меня уже есть дерево для работы!).
Итак, я построил свое дерево с помощью Composite + Recursive Builder, он распознает вход и строит дерево без каких-либо проблем.
Но возникает вопрос: как применить интерпретатор к моей структуре?
Здесь моя диаграмма классов (что-то итальянское, но это вполне понятно)
![Composite+Builder class diagram]()
Если я правильно понял, Interpreter использует класс для каждого правила грамматики, поэтому мне нужно сделать cond-класс, затем termb и т.д.
Но я привязываю их к своему композиту?
Ответы
Ответ 1
Не знаю, почему вам сказали не использовать одну и ту же древовидную структуру. Я думаю, что я бы добавил метод compare() в свой интерфейс выражения. Это имеет смысл для меня. Выражение должно знать, как оценивать себя.
Я бы сказал, что ваш текущий интерфейс выражения слишком много (например, операнды). Как клиент выражения, мне нужно только 1) вызвать его и 2) прочитать результат, и я думаю, возможно 3) напечатать его. На самом деле, я бы предпочел использовать toString() для прямой печати.
Вы, наверное, уже заметили, но не все выражения принимают 2 операнда (например, NOT или NEGATE). Это уже создает своего рода несоответствие с вашим интерфейсом. Я бы упростил это:
public interface Expression {
int evaluate();
}
Затем каждая из ваших операций и терминалов знает, как оценивать себя (и преобразовывать себя в строку).
Таким образом, я мог бы иметь конкретные операции, например:
public class Terminal implements Expression {
private final int value;
public Terminal(int value) { this.value = value; }
public int evaluate() { return value; }
public String toString() { return String.valueOf(value); }
}
public class Add implements Expression {
private final Expression left;
private final Expression right;
public Add(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public String toString() {
return left.toString() + " + " + right.toString();
}
// leave the rest for you
}
Теперь я могу легко построить дерево
Expression expr = new Add(new Terminal(1), new Subtract(new Terminal(2), new Terminal(3)));
int result = expr.evaluate();
System.out.print(expr.toString() + " = " + result);
И мне даже не нужен прямой доступ к отдельным операндам.
Ответ 2
Если я правильно понимаю вашу проблему, я бы сказал, что каждый конкретный класс должен быть подклассом вашей основной составной структуры.
Если выражение является основным составным, тогда:
Выражение: срок
Выражение: OperandTerm
Условие: Term BinOperand Expression
Условие: UnaryOperand Expression
Срок: Int | Foat | ...
.
,
.
Ответ 3
interpreter предоставляет вам способ определения выражения вашего языка на основе терминальных, а не терминальных объектов. Интерпретатор представляет собой составной шаблон, поэтому я думаю, что он уже применяется.
Возможно, вам не нужно создавать один класс для терминального и конечного элементов, использовать атрибуты типа (operatorType, expressionType) в терминах NonTerminal/Terminal, чтобы отличаться от ваших символов грамматики.
Учитывая и выраженное с вашей грамматикой выражение [A = 0], дерево объектов, сформированное с помощью классов шаблонов интерпретатора, будет выглядеть так (прошу простить за плохое качество и ошибки UML sintax, но у меня нет подходящего редактора UML ):
![enter image description here]()
Это дерево объектов должно быть сконструировано анализатором выражений. После того, как у вас есть это дерево, используйте рекурсивный потомственный парсер, чтобы пройти через это дерево, оценивая каждое дерево node.
Итак, оценка выражения производится парсером, и интерпретатор предоставляет вам структуру данных для представления выражений грамматики.
Надеюсь на эту помощь.