ExpressionNode:
    | { k: "num"; v: number }
    | { k: "sym"; name: string }
    | { a: ExpressionNode; k: "neg" }
    | {
        a: ExpressionNode;
        b: ExpressionNode;
        k: "bin";
        op: "+" | "-" | "*" | "/" | "^";
    }
    | { args: ExpressionNode[]; k: "fn"; name: ExpressionFunctionName }

The syntax tree behind a parsed expression. Exported so the type of SymbolExpression.ast has a name; a host builds one with parseExpression, never by hand.