///|
/// Expression optimizer.
///
/// Bottom-up rewrites that preserve observable evaluation semantics:
/// fully-literal subtrees fold through the standard evaluator (a raising
/// subtree stays unfolded so runtime errors are preserved), and identity
/// eliminations drop a literal operand only when the eliminated side is
/// provably never evaluated or the kept side is a literal. Traces and
/// step counts legitimately shrink; values and error classes do not.
///|
fn try_fold(expr : Expression) -> Expression? {
let state : Evaluation = { fuel: 1000, trace: [], }
let folded : Expression? = try {
let value = evaluate(expr, Object(Map([])), state)
Some(Literal(value))
} catch {
_ => None
}
folded
}
///|
fn optimize_expr(expr : Expression) -> Expression {
match expr {
Literal(_) => expr
Variable(_) => expr
Unary(op, operand) => {
let child = optimize_expr(operand)
match (op, child) {
("!", Literal(Bool(b))) => Literal(Bool(!b))
("-", Literal(Number(n))) => Literal(Number(-n))
("!", Unary("!", inner)) => inner
("-", Unary("-", Unary("-", inner))) => inner
(_, _) => Unary(op, child)
}
}
Binary(op, left, right) => {
let l = optimize_expr(left)
let r = optimize_expr(right)
match (op, l, r) {
("&&", Literal(Bool(false)), _) => Literal(Bool(false))
("&&", Literal(Bool(true)), kept) => kept
("&&", kept, Literal(Bool(true))) => kept
("||", Literal(Bool(true)), _) => Literal(Bool(true))
("||", Literal(Bool(false)), kept) => kept
("||", kept, Literal(Bool(false))) => kept
(_, _, _) =>
match try_fold(Binary(op, l, r)) {
Some(folded) => folded
None => Binary(op, l, r)
}
}
}
Call(name, args) => {
let optimized : Array[Expression] = []
for arg in args {
optimized.push(optimize_expr(arg))
}
match try_fold(Call(name, optimized)) {
Some(folded) => folded
None => Call(name, optimized)
}
}
}
}
///|
fn optimize_request(request : Value) -> Value raise {
let source = str(get(request, "expression"))
let original = compile(source)
let optimized = optimize_expr(original)
let (original_nodes, original_depth) = original.metrics()
let (optimized_nodes, optimized_depth) = optimized.metrics()
let original_canonical = original.render()
let optimized_canonical = optimized.render()
let savings = if original_nodes == 0 {
0.0
} else {
(original_nodes - optimized_nodes).to_double() *
100.0 /
original_nodes.to_double()
}
record([
("original", String(original_canonical)),
("canonical", String(optimized_canonical)),
("changed", Bool(original_canonical != optimized_canonical)),
("original_nodes", Number(original_nodes.to_double())),
("nodes", Number(optimized_nodes.to_double())),
("original_depth", Number(original_depth.to_double())),
("depth", Number(optimized_depth.to_double())),
("savings_percent", Number(savings)),
])
}