在办公室,我们已经将简单的域特定语言(DSL)应用于我们遇到的几个问题域。
基本上,我们将自定义脚本解析(lex / yacc)到表达式树中。表达式树中的每个节点都有一个.evaluate()方法,可以递归调用,直到程序完成。很好,而且很简单。这是一件好事,因为我对解析技术,编译器构造和诸如此类的东西几乎一无所知(我的同事知道一件事或两件事是件好事。)
现在是挑战:我们目前正在开展的实施需要能力
坚持实际状态不应该太难,但树中的位置(或“callstack”)会让我感到困惑。如何实施这样的系统?使用某种ID为每个节点存储树中的位置?或者正在评估树本身是错误的方法,我们应该以某种方式将其转换为线性的东西吗?
我猜这是一个相当普遍的问题,但我不知道该寻找什么。任何关于正确术语的帮助,正确方向的推动,搜索的关键词,要看的设计模式等都是最受欢迎的!
(在Win32 / Dephi中这样做,但希望我们可以保持这种语言不可知)
答案 0 :(得分:1)
可以使用子编号列表指定树中的位置。您的评估看起来像这样(快速编写;未编译/测试):
public class Context {
private Context parent; // set in constructor
private int child; // with get/set
}
public ReturnType evaluate(Context context)
context.setChild(context.getChild() + 1);
context = new Context(context); // push a new context for calls
// do evaluation - when calling kids, pass context
}
以上内容应将上下文作为一个数字列表进行跟踪,以确定您当前处理的是哪个孩子。
我建议不要在每个节点类型中实现它,而是建议使用装饰器(或使用模板方法的超类)为您完成,例如:
// decorator
public class ContextTrackerEvaluator<T> implements Evaluator<T> {
private Evaluator realEvaluator;
public ContextTrackerEvaluator(Evaluator realEvaluator) {
this.realEvaluator = realEvaluator;
}
public T evaluate(Context context) {
context.setChild(context.getChild() + 1);
context = new Context(context); // push a new context for calls
realEvaluator.evaluate(context);
}
}
// OR superclass w/ template method
public class EvaluatorBase<T> {
public final T evaluate(Context context) {
context.setChild(context.getChild() + 1);
context = new Context(context); // push a new context for calls
doEvaluate(context);
}
// subclasses override doEvaluate to do their real work
protected abstract T doEvaluate(Context context);
}
如果您使用装饰器,请确保装饰每个节点......
这使您可以访问上下文列表。
然后您可以添加一个可以比较的“停止上下文”参数(这样您就可以传入两个上下文,并且可以比较它们以查看它们是否匹配。
希望这有帮助! - 斯科特