Skip to content
This repository was archived by the owner on Feb 22, 2024. It is now read-only.

Play with explicit AST #20

Merged
merged 7 commits into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright 2020 VMware, Inc.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

use serde_json::Value;

/// A path is a tree of selector nodes.
///
/// For example, the JSONPath `$.foo.bar` yields this AST:
///
/// ```text
/// *
/// / \
/// * \___ DotName("bar")
/// / \
/// * \___ DotName("foo")
/// /
/// Root ___/
/// ```
///
/// A more complicated example: `$.foo[1,2]["bar"]`:
///
/// ```text
/// *
/// / \
/// * \___ Union
/// / \ \
/// * \___ Union \
/// / \ [Field("bar")]
/// * \
/// / \ [Number(1), Number(2)]
/// / \
/// Root ___/ \___ DotName("foo")
Copy link
Contributor

@glyn glyn Oct 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two diagrams still seem inconsistent. I'd expect the part of the tree nearest the root to be identical in both cases since $.foo is a common prefix of both selectors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because the expressions have right to left precedence

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still don't get it. $.foo.bar produces the AST:

Sel(Sel(Root, DotName("foo")), DotName("bar"))

and $.foo[1,2]["bar"] produces:

Sel(Sel(Sel(Root, DotName("foo")), Union([Number(1), Number(2)])), Union([Field("bar")]))

Root appears inside Sel(Root, DotName("foo")) in both cases. Shouldn't this subtree look the same in both diagrams?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh :brainfart:; fixed

/// ```
///
#[derive(Debug)]
pub enum Path {
Root,
Sel(Box<Path>, Selector),
}

#[derive(Debug)]
pub enum Selector {
Union(Vec<Index>),
DotName(String),
DotWildcard,
}

#[derive(Debug)]
pub enum Index {
Field(String),
Number(i64),
}

type Iter<'a> = Box<dyn Iterator<Item = &'a Value> + 'a>;

impl Path {
pub fn find<'a>(&'a self, input: &'a Value) -> Iter<'a> {
match self {
Path::Root => Box::new(std::iter::once(input)),
Path::Sel(left, sel) => Box::new(left.find(input).flat_map(move |v| sel.find(v))),
}
}
}

impl Selector {
pub fn find<'a>(&'a self, input: &'a Value) -> Iter<'a> {
match self {
Selector::Union(indices) => Box::new(indices.iter().flat_map(move |i| i.get(input))),
Selector::DotName(name) => Box::new(input.get(name).into_iter()),
Selector::DotWildcard => match input {
Value::Object(m) => Box::new(m.values()),
Value::Array(a) => Box::new(a.iter()),
_ => Box::new(std::iter::empty()),
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the readability of this approach. (It's a pity Box::new can't be factored out of the outer match, but that's rust for you.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully soon rust will have the box keyword which at least removes the need for extra parentheses

}
}
}

impl Index {
pub fn get<'a>(&self, v: &'a Value) -> Iter<'a> {
match self {
Index::Field(name) => Box::new(v.get(name).into_iter()),
Index::Number(num) => Box::new(v.get(abs_index(*num, v)).into_iter()),
}
}
}

fn abs_index(index: i64, node: &Value) -> usize {
if index >= 0 {
index as usize
} else {
let len = if let Value::Array(a) = node {
a.len() as i64
} else {
0
};
(len + index) as usize
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::parser::parse;
use serde_json::json;

#[test]
fn demo() {
let a1 = Path::Sel(Box::new(Path::Root), Selector::DotName("foo".to_owned()));
let a2 = Path::Sel(Box::new(a1), Selector::DotName("bar".to_owned()));
let a3 = Path::Sel(
Box::new(a2),
Selector::Union(vec![Index::Field("baz".to_owned())]),
);
let a4 = Path::Sel(Box::new(a3), Selector::Union(vec![Index::Number(4)]));

let j = json!({"foo":{"bar":{"baz":[10,20,30,40,50,60]}}});
println!("j: {}", j);

let v = a4.find(&j).collect::<Vec<_>>();
assert_eq!(v[0], 50);
}

#[test]
fn parse_demo() -> Result<(), String> {
let p = parse("$.foo['bar'].*[4,-1]")?;
println!("AST: {:?}", &p);
let j = json!({"foo":{"bar":{"baz":[10,20,30,40,50,60]}}});

let v = p.find(&j).collect::<Vec<_>>();
println!("RES: {:?}", v);

assert_eq!(v[0], 50);
assert_eq!(v[1], 60);
Ok(())
}
}
20 changes: 17 additions & 3 deletions src/jsonpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
* SPDX-License-Identifier: BSD-2-Clause
*/

use crate::ast;
use crate::parser;
use crate::path::Path;
use serde_json::Value;

#[derive(Debug)]
pub struct SyntaxError {
Expand All @@ -18,6 +19,19 @@ impl std::fmt::Display for SyntaxError {
}
}

pub fn parse(selector: &str) -> Result<impl Path, SyntaxError> {
parser::parse(selector).map_err(|m| SyntaxError { message: m })
pub enum FindError {
// no errors yet
}

pub fn parse(selector: &str) -> Result<Path, SyntaxError> {
let p = parser::parse(selector).map_err(|m| SyntaxError { message: m })?;
Ok(Path(p))
}

pub struct Path(ast::Path);

impl Path {
pub fn find<'a>(&'a self, document: &'a Value) -> Result<Vec<&'a Value>, FindError> {
Ok(self.0.find(document).collect())
}
}
3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ extern crate pest;
#[macro_use]
extern crate pest_derive;

pub mod ast;
pub mod jsonpath;
mod matchers;
mod parser;
pub mod path;
176 changes: 0 additions & 176 deletions src/matchers.rs

This file was deleted.

Loading