// Port of jmespath/ast.py.
//
// Upstream AST nodes are dicts `{"type": ..., "children": [...], "value": ...}`
// built by small constructor functions (`ast.field(name)`, ...). Here each
// node type is a constructor of the `Node` enum; `Node::type_name`,
// `Node::children`, `Node::value` and `Node::to_json` recover the dict view.
// `Subexpression` and `IndexExpression` hold a (mutable) `Array` because the
// parser appends to them, as upstream does.
///|
/// A JMESPath AST node.
pub(all) enum Node {
/// `comparator(name, first, second)`; `name` is one of
/// `eq`, `ne`, `lt`, `gt`, `lte`, `gte`.
Comparator(String, Node, Node)
/// `current_node()`
Current
/// `expref(expression)`
Expref(Node)
/// `function_expression(name, args)`
FunctionExpression(String, Array[Node])
/// `field(name)`
Field(String)
/// `filter_projection(left, right, comparator)`
FilterProjection(Node, Node, Node)
/// `flatten(node)`
Flatten(Node)
/// `identity()`
Identity
/// `index(index)`
Index(Int)
/// `index_expression(children)`
IndexExpression(Array[Node])
/// `key_val_pair(key_name, node)`
KeyValPair(String, Node)
/// `literal(literal_value)`
Literal(Json)
/// `multi_select_dict(nodes)`, the nodes are `KeyValPair`s.
MultiSelectDict(Array[Node])
/// `multi_select_list(nodes)`
MultiSelectList(Array[Node])
/// `or_expression(left, right)`
OrExpression(Node, Node)
/// `and_expression(left, right)`
AndExpression(Node, Node)
/// `not_expression(expr)`
NotExpression(Node)
/// `pipe(left, right)`
Pipe(Node, Node)
/// `projection(left, right)`
Projection(Node, Node)
/// `subexpression(children)`
Subexpression(Array[Node])
/// `slice(start, end, step)`
Slice(Int?, Int?, Int?)
/// `value_projection(left, right)`
ValueProjection(Node, Node)
} derive(Eq, Debug)
///|
/// The upstream `node['type']`.
pub fn Node::type_name(self : Node) -> String {
match self {
Comparator(_, _, _) => "comparator"
Current => "current"
Expref(_) => "expref"
FunctionExpression(_, _) => "function_expression"
Field(_) => "field"
FilterProjection(_, _, _) => "filter_projection"
Flatten(_) => "flatten"
Identity => "identity"
Index(_) => "index"
IndexExpression(_) => "index_expression"
KeyValPair(_, _) => "key_val_pair"
Literal(_) => "literal"
MultiSelectDict(_) => "multi_select_dict"
MultiSelectList(_) => "multi_select_list"
OrExpression(_, _) => "or_expression"
AndExpression(_, _) => "and_expression"
NotExpression(_) => "not_expression"
Pipe(_, _) => "pipe"
Projection(_, _) => "projection"
Subexpression(_) => "subexpression"
Slice(_, _, _) => "slice"
ValueProjection(_, _) => "value_projection"
}
}
///|
/// The upstream `node['children']` that are nodes. (Upstream `slice` nodes
/// store their integer bounds as children; they are not returned here.)
pub fn Node::children(self : Node) -> Array[Node] {
match self {
Comparator(_, a, b) => [a, b]
Current | Identity | Field(_) | Index(_) | Literal(_) | Slice(_, _, _) => []
Expref(n) | Flatten(n) | KeyValPair(_, n) | NotExpression(n) => [n]
FunctionExpression(_, args) => args
FilterProjection(left, right, comparator) => [left, right, comparator]
IndexExpression(children)
| MultiSelectDict(children)
| MultiSelectList(children)
| Subexpression(children) => children
OrExpression(a, b)
| AndExpression(a, b)
| Pipe(a, b)
| Projection(a, b)
| ValueProjection(a, b) => [a, b]
}
}
///|
/// The upstream `node['value']`, for the node types that have one.
pub fn Node::value(self : Node) -> Json? {
match self {
Comparator(name, _, _)
| FunctionExpression(name, _)
| Field(name)
| KeyValPair(name, _) => Some(Json::string(name))
Index(i) => Some(Json::number(i.to_double()))
Literal(v) => Some(v)
_ => None
}
}
///|
fn optional_int_json(i : Int?) -> Json {
match i {
Some(i) => Json::number(i.to_double())
None => Json::null()
}
}
///|
/// The upstream dict representation of the node, e.g.
/// `{"type": "field", "children": [], "value": "foo"}`.
pub impl ToJson for Node with fn to_json(self) {
// Same key order as the upstream constructors.
let obj : Map[String, Json] = Map([])
obj["type"] = Json::string(self.type_name())
if self is (Index(_) | Literal(_)) {
obj["value"] = self.value().unwrap()
}
obj["children"] = match self {
Slice(start, end, step) =>
Json::array([
optional_int_json(start),
optional_int_json(end),
optional_int_json(step),
])
_ => Json::array(self.children().map(child => child.to_json()))
}
if self.value() is Some(v) {
obj["value"] = v
}
Json::object(obj)
}
///|
pub extend Node with ToJson::{to_json}