sclass GazelleTree { L children; GazelleTree parent; double weight = 1, totalWeight; S line, lineType; S prediction, judgement; RuleEngine2_MatchedRule mr; bool isSplitNode; long created = now(); transient GazelleEvalContext ctx; *() {} *(S *line) {} *(GazelleEvalContext *ctx, S *line) {} // e.g. for rendering tree toString { ret (isSplitNode ? "[split] " : "") + line + appendSquareBracketed(joinWithComma(listPlusNempties(renderQualityElements(), ruleID()))); } LS renderQualityElements() { new LS l; addIfNempty(l, prediction); if (mr != null) { l.add(mr.qualitySum() + " / " + formatDouble(mr.relativeQuality(), 2)); if (mr.moreConditions()) l.add("unsatisfied"); } ret l; } // includes this node LS history() { new LS l; GazelleTree e = this; while (e != null) { l.add(e.line); e = e.parent; } ret reversed(l); } void add(GazelleTree child) { if (children == null) children = new L; child.parent = this; child.setContext(ctx); children.add(child); } void setContext(GazelleEvalContext ctx) { this.ctx = ctx; pcallFAll(ctx.onNewNode, this); _add(ctx.linesSet, line); } RuleEngine2.Rule rule() { ret mr == null ? null : mr.rule; } S ruleID() { ret rule() == null ? null : rule().globalID; } SS varMap() { ret mr == null ? null : mr.map; } void flush() { if (mr != null) { mr.flush(); line = mr.outText(); } } Collection ruleComments() { RuleEngine2.Rule rule = rule(); ret rule == null ? null : rule.comments; } }