Skip to content

Commit

Permalink
feat: whitespace modes in the parser
Browse files Browse the repository at this point in the history
- `rsonpath-syntax` disallows leading and trailing
whitespace by default, but can relax this with parser options;
- main parser used in `rq` now ignores leading and trailing query whitespace

Ref: #166
  • Loading branch information
V0ldek committed Jan 10, 2024
1 parent 4811f10 commit 58b5dfc
Show file tree
Hide file tree
Showing 18 changed files with 961 additions and 120 deletions.
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions crates/rsonpath-syntax/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,14 @@
[![License](https://img.shields.io/crates/l/rsonpath)](https://choosealicense.com/licenses/mit/)

Complete, fast, and fully spec-compliant JSONPath query parser.

## State of the crate

This is an in-development version that supports only name, index, and wildcard selectors.
However, these are fully supported, tested, and fuzzed. The planned roadmap is:

- [ ] support slices
- [ ] support filters (without functions)
- [ ] support functions (including type check)
- [ ] polish the API
- [ ] 1.0.0 stable release
12 changes: 6 additions & 6 deletions crates/rsonpath-syntax/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Select
/// // Can also use `builder.build()` as a non-consuming version.
/// let query: JsonPathQuery = builder.into();
///
/// assert_eq!(format!("{query}"), "$['a']..['b'][*]['c']..[*]");
/// assert_eq!(query.to_string(), "$['a']..['b'][*]['c']..[*]");
/// ```
pub struct JsonPathQueryBuilder {
segments: Vec<Segment>,
Expand Down Expand Up @@ -61,7 +61,7 @@ impl JsonPathQueryBuilder {
/// assert_eq!(result.segments().len(), 1);
/// let segment = &result.segments()[0];
/// assert!(segment.is_child());
/// assert_eq!(segment.selectors(), &[
/// assert_eq!(&segment.selectors().as_slice(), &[
/// Selector::Name(JsonString::new("abc")),
/// Selector::Index(Index::FromStart(JsonUInt::from(10))),
/// Selector::Wildcard,
Expand Down Expand Up @@ -92,7 +92,7 @@ impl JsonPathQueryBuilder {
/// assert_eq!(result.segments().len(), 1);
/// let segment = &result.segments()[0];
/// assert!(segment.is_descendant());
/// assert_eq!(segment.selectors(), &[
/// assert_eq!(&segment.selectors().as_slice(), &[
/// Selector::Name(JsonString::new("abc")),
/// Selector::Index(Index::FromStart(JsonUInt::from(10))),
/// Selector::Wildcard,
Expand Down Expand Up @@ -207,15 +207,15 @@ impl JsonPathSelectorsBuilder {
/// ## Examples
///
/// ```rust
/// # use rsonpath_syntax::{Selector, Index, num::{JsonInt, JsonUInt}, builder::JsonPathQueryBuilder};
/// # use rsonpath_syntax::{Selector, Index, num::{JsonNonZeroUInt, JsonUInt}, builder::JsonPathQueryBuilder};
/// let mut builder = JsonPathQueryBuilder::new();
/// builder.child(|x| x.index(10).index(-20));
/// let result = builder.into_query();
/// assert_eq!(result.segments().len(), 1);
/// let segment = &result.segments()[0];
/// assert_eq!(segment.selectors(), &[
/// assert_eq!(segment.selectors().as_slice(), &[
/// Selector::Index(Index::FromStart(JsonUInt::from(10))),
/// Selector::Index(Index::FromEnd(JsonUInt::from(20))),
/// Selector::Index(Index::FromEnd(JsonNonZeroUInt::try_from(20).unwrap())),
/// ]);
/// ```
#[inline(always)]
Expand Down
3 changes: 2 additions & 1 deletion crates/rsonpath-syntax/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ fn main() -> ExitCode {
Ok(x) => println!("OK: {x:?}\nDISPLAY:{x}\nINPUT: {input}"),

Err(err) => {
println!("DBGERR: {err:?}");
#[cfg(feature = "color")]
println!("ERR: {}\nINPUT: {input}", err.colored());
#[cfg(not(feature = "color"))]
println!("ERR: {}\nINPUT: {input}", err);
println!("ERR: {err}\nINPUT: {input}");
}
}

Expand Down
17 changes: 12 additions & 5 deletions crates/rsonpath-syntax/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ pub(crate) struct SyntaxError {

#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) enum SyntaxErrorKind {
DisallowedLeadingWhitespace,
DisallowedTrailingWhitespace,
InvalidUnescapedCharacter,
InvalidEscapeSequence,
UnpairedHighSurrogate,
Expand Down Expand Up @@ -149,16 +151,13 @@ impl SyntaxError {
builder.add_non_underline(width);
} else if i <= end_idx {
builder.add_underline(width);
if i == end_idx {
builder.add_underline_message(self.kind.underline_message());
}
}
builder.add_char(c);
}
if end_idx >= input.len() {
builder.add_underline(1);
builder.add_underline_message(self.kind.underline_message());
}
builder.add_underline_message(self.kind.underline_message());

self.generate_notes(&mut builder, suggestion, input);

Expand All @@ -179,6 +178,9 @@ impl SyntaxError {
let (prefix, error, suffix) = self.split_error(input);
// Kind-specific notes and suggestion building.
match self.kind {
SyntaxErrorKind::DisallowedLeadingWhitespace | SyntaxErrorKind::DisallowedTrailingWhitespace => {
suggestion.remove(start_idx, error.len());
}
SyntaxErrorKind::InvalidUnescapedCharacter => {
if error == "\"" {
suggestion.replace(start_idx, 1, r#"\""#);
Expand Down Expand Up @@ -374,6 +376,7 @@ impl DisplayableSyntaxErrorBuilder {
}
}

#[derive(Debug)]
pub(crate) enum InternalParseError<'a> {
SyntaxError(SyntaxError, &'a str),
SyntaxErrors(Vec<SyntaxError>, &'a str),
Expand Down Expand Up @@ -637,6 +640,8 @@ impl SyntaxErrorKind {
#[inline]
fn toplevel_message(&self) -> String {
match self {
Self::DisallowedLeadingWhitespace => "query starting with whitespace".to_string(),
Self::DisallowedTrailingWhitespace => "query ending with whitespace".to_string(),
Self::InvalidUnescapedCharacter => "invalid unescaped control character".to_string(),
Self::InvalidEscapeSequence => "invalid escape sequence".to_string(),
Self::UnpairedHighSurrogate => "invalid unicode escape sequence - unpaired high surrogate".to_string(),
Expand All @@ -661,14 +666,16 @@ impl SyntaxErrorKind {
#[inline]
fn underline_message(&self) -> String {
match self {
Self::DisallowedLeadingWhitespace => "leading whitespace is disallowed".to_string(),
Self::DisallowedTrailingWhitespace => "trailing whitespace is disallowed".to_string(),
Self::InvalidUnescapedCharacter => "this character must be escaped".to_string(),
Self::InvalidEscapeSequence => "not a valid escape sequence".to_string(),
Self::UnpairedHighSurrogate => "this high surrogate is unpaired".to_string(),
Self::UnpairedLowSurrogate => "this low surrogate is unpaired".to_string(),
Self::InvalidHexDigitInUnicodeEscape => "not a hex digit".to_string(),
Self::MissingClosingDoubleQuote => "expected a double quote '\"'".to_string(),
Self::MissingClosingSingleQuote => "expected a single quote `'`".to_string(),
Self::MissingRootIdentifier => "the '$' character missing here".to_string(),
Self::MissingRootIdentifier => "the '$' character missing before here".to_string(),
Self::InvalidSegmentStart => "not a valid segment syntax".to_string(),
Self::InvalidSegmentAfterTwoPeriods => "not a valid descendant segment syntax".to_string(),
Self::InvalidNameShorthandAfterOnePeriod => "not a valid name shorthand".to_string(),
Expand Down
Loading

0 comments on commit 58b5dfc

Please sign in to comment.