Skip to content

Commit

Permalink
Allow a custom keys selector pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
jg-rp committed Mar 25, 2024
1 parent ba048e1 commit 9187f29
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 588 deletions.
49 changes: 29 additions & 20 deletions src/path/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import { Match as MatchFilterFunction } from "./functions/match";
import { Search as SearchFilterFunction } from "./functions/search";
import { Value as ValueFilterFunction } from "./functions/value";
import { tokenize } from "./lex";
import {
tokenize as non_standard_tokenize,
Parser as NonStandardParser,
} from "./extra";
import { tokenize as non_standard_tokenize } from "./extra/lex";
import { JSONPathNode, JSONPathNodeList } from "./node";
import { Parser } from "./parse";
import { JSONPath } from "./path";
Expand All @@ -31,11 +28,15 @@ import { CurrentKey } from "./extra/expression";
export type JSONPathEnvironmentOptions = {
/**
* Indicates if the environment should to be strict about its compliance with
* JSONPath standards.
* RFC 9535.
*
* Defaults to `true`. Setting `strict` to `false` currently has no effect.
* If/when we add non-standard features, the environment's strictness will
* control their availability.
* Defaults to `true`. Setting `strict` to `false` enables non-standard
* features. Non-standard features are subject to change if:
*
* - conflicting features are included in a future JSONPath standard or a
* draft standard.
* - an overwhelming consensus amongst the JSONPath community emerges for
* conflicting features
*/
strict?: boolean;

Expand All @@ -59,8 +60,17 @@ export type JSONPathEnvironmentOptions = {

/**
* If `true`, enable nondeterministic ordering when iterating JSON object data.
*
* This is mainly useful for validating the JSONPath Compliance Test Suite.
*/
nondeterministic?: boolean;

/**
* The pattern to use for the non-standard _keys selector_.
*
* The lexer expects the sticky bit to be set. Defaults to `/~/y`.
*/
keysPattern?: RegExp;
};

/**
Expand Down Expand Up @@ -103,15 +113,19 @@ export class JSONPathEnvironment {
*/
readonly nondeterministic: boolean;

/**
* The pattern to use for the non-standard _keys selector_.
*/
readonly keysPattern: RegExp;

/**
* A map of function names to objects implementing the {@link FilterFunction}
* interface. You are free to set or delete custom filter functions directly.
*/
public functionRegister: Map<string, FilterFunction> = new Map();

// TODO: have non-standard parser inherit from Parser?
private parser: Parser | NonStandardParser;
private tokenize: (path: string) => Token[];
private parser: Parser;
private tokenize: (environment: JSONPathEnvironment, path: string) => Token[];

/**
* @param options - Environment configuration options.
Expand All @@ -122,15 +136,10 @@ export class JSONPathEnvironment {
this.minIntIndex = options.maxIntIndex ?? -Math.pow(2, 53) - 1;
this.maxRecursionDepth = options.maxRecursionDepth ?? 50;
this.nondeterministic = options.nondeterministic ?? false;
this.keysPattern = options.keysPattern ?? /~/y;

if (this.strict) {
this.parser = new Parser(this);
this.tokenize = tokenize;
} else {
this.parser = new NonStandardParser(this);
this.tokenize = non_standard_tokenize;
}

this.parser = new Parser(this);
this.tokenize = this.strict ? tokenize : non_standard_tokenize;
this.setupFilterFunctions();
}

Expand All @@ -141,7 +150,7 @@ export class JSONPathEnvironment {
public compile(path: string): JSONPath {
return new JSONPath(
this,
this.parser.parse(new TokenStream(this.tokenize(path))),
this.parser.parse(new TokenStream(this.tokenize(this, path))),
);
}

Expand Down
2 changes: 0 additions & 2 deletions src/path/extra/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
export { tokenize } from "./lex";
export { Parser } from "./parse";
28 changes: 19 additions & 9 deletions src/path/extra/lex.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/** A lexer that accepts additional, non-standard tokens. */
import { JSONPathEnvironment } from "../environment";
import { JSONPathLexerError, JSONPathSyntaxError } from "../errors";
import { Token, TokenKind } from "../token";

Expand All @@ -9,7 +10,6 @@ const functionNamePattern = /[a-z][a-z_0-9]*/y;
const indexPattern = /-?\d+/y;
const intPattern = /-?[0-9]+/y;
const namePattern = /[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*/y;
const keysPattern = /~/y;
const whitespace = new Set([" ", "\n", "\t", "\r"]);

/**
Expand Down Expand Up @@ -43,7 +43,10 @@ class Lexer {
/**
* @param path - A JSONPath query.
*/
constructor(readonly path: string) {}
constructor(
readonly environment: JSONPathEnvironment,
readonly path: string,
) {}

public get pos(): number {
return this.#pos;
Expand Down Expand Up @@ -175,8 +178,11 @@ type StateFn = (l: Lexer) => StateFn | null;
* @returns A two-tuple containing a lexer for _path_ and an array to populate
* with tokens.
*/
export function lex(path: string): [Lexer, Token[]] {
const lexer = new Lexer(path);
export function lex(
environment: JSONPathEnvironment,
path: string,
): [Lexer, Token[]] {
const lexer = new Lexer(environment, path);
return [lexer, lexer.tokens];
}

Expand All @@ -185,8 +191,11 @@ export function lex(path: string): [Lexer, Token[]] {
* @param path - A JSONPath query.
* @returns Tokens to be parsed by the parser.
*/
export function tokenize(path: string): Token[] {
const [lexer, tokens] = lex(path);
export function tokenize(
environment: JSONPathEnvironment,
path: string,
): Token[] {
const [lexer, tokens] = lex(environment, path);
lexer.run();
if (tokens.length && tokens[tokens.length - 1].kind === TokenKind.ERROR) {
throw new JSONPathSyntaxError(
Expand Down Expand Up @@ -262,7 +271,7 @@ function lexDescendantSelection(l: Lexer): StateFn | null {
return lexSegment;
}

if (l.acceptMatchRun(keysPattern)) {
if (l.acceptMatchRun(l.environment.keysPattern)) {
l.emit(TokenKind.KEYS);
return lexSegment;
}
Expand All @@ -288,7 +297,8 @@ function lexDotSelector(l: Lexer): StateFn | null {

l.backup();

if (l.acceptMatchRun(keysPattern)) {
// TODO: move this above "*"
if (l.acceptMatchRun(l.environment.keysPattern)) {
l.emit(TokenKind.KEYS);
return lexSegment;
}
Expand Down Expand Up @@ -339,7 +349,7 @@ function lexInsideBracketedSelection(l: Lexer): StateFn | null {
continue;
}

if (l.acceptMatchRun(keysPattern)) {
if (l.acceptMatchRun(l.environment.keysPattern)) {
l.emit(TokenKind.KEYS);
continue;
}
Expand Down
Loading

0 comments on commit 9187f29

Please sign in to comment.