Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Allow visitor to cease callbacks
  • Loading branch information
Vbbab committed May 13, 2024
commit 2ec474e89b711253ce23cc173980293d9d043896
39 changes: 31 additions & 8 deletions src/impl/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,25 +389,48 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions
// Important: Only pass copies of this to visitor functions to prevent accidental modification, and
// to not affect visitor functions which stored a reference to a previous JSONPath
const _jsonPath: JSONPath = [];

// Depth of onXXXBegin() callbacks suppressed. onXXXEnd() decrements this if it isn't 0 already.
// Callbacks are only called when this value is 0.
let suppressedCallbacks = 0;

function toNoArgVisit(visitFunction?: (offset: number, length: number, startLine: number, startCharacter: number) => void): () => void {
return visitFunction ? () => visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
return visitFunction ? () => suppressedCallbacks === 0 && visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
}
function toNoArgVisitWithPath(visitFunction?: (offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => void): () => void {
return visitFunction ? () => visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice()) : () => true;
return visitFunction ? () => suppressedCallbacks === 0 && visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice()) : () => true;
}
function toOneArgVisit<T>(visitFunction?: (arg: T, offset: number, length: number, startLine: number, startCharacter: number) => void): (arg: T) => void {
return visitFunction ? (arg: T) => visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
return visitFunction ? (arg: T) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
}
function toOneArgVisitWithPath<T>(visitFunction?: (arg: T, offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => void): (arg: T) => void {
return visitFunction ? (arg: T) => visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice()) : () => true;
return visitFunction ? (arg: T) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice()) : () => true;
}
function toBeginVisit(visitFunction?: (offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => boolean | void): () => void {
return visitFunction ?
() => {
if (suppressedCallbacks > 0) { suppressedCallbacks++; }
else {
let cbReturn = visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice());
if (typeof cbReturn === 'boolean' && !cbReturn) { suppressedCallbacks = 1; }
}
}
: () => true;
}
function toEndVisit(visitFunction?: (offset: number, length: number, startLine: number, startCharacter: number) => void): () => void {
return visitFunction ?
() => {
if (suppressedCallbacks > 0) { suppressedCallbacks--; }
if (suppressedCallbacks === 0) { visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()); }
}
: () => true;
}

const onObjectBegin = toNoArgVisitWithPath(visitor.onObjectBegin),
const onObjectBegin = toBeginVisit(visitor.onObjectBegin),
onObjectProperty = toOneArgVisitWithPath(visitor.onObjectProperty),
onObjectEnd = toNoArgVisit(visitor.onObjectEnd),
onArrayBegin = toNoArgVisitWithPath(visitor.onArrayBegin),
onArrayEnd = toNoArgVisit(visitor.onArrayEnd),
onObjectEnd = toEndVisit(visitor.onObjectEnd),
onArrayBegin = toBeginVisit(visitor.onArrayBegin),
onArrayEnd = toEndVisit(visitor.onArrayEnd),
onLiteralValue = toOneArgVisitWithPath(visitor.onLiteralValue),
onSeparator = toOneArgVisit(visitor.onSeparator),
onComment = toNoArgVisit(visitor.onComment),
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export interface JSONVisitor {
/**
* Invoked when an open brace is encountered and an object is started. The offset and length represent the location of the open brace.
*/
onObjectBegin?: (offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => void;
onObjectBegin?: (offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => boolean | void;

/**
* Invoked when a property is encountered. The offset and length represent the location of the property name.
Expand All @@ -265,7 +265,7 @@ export interface JSONVisitor {
/**
* Invoked when an open bracket is encountered. The offset and length represent the location of the open bracket.
*/
onArrayBegin?: (offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => void;
onArrayBegin?: (offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => boolean | void;

/**
* Invoked when a closing bracket is encountered. The offset and length represent the location of the closing bracket.
Expand Down
29 changes: 25 additions & 4 deletions src/test/json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,19 @@ interface VisitorError extends ParseError {
startCharacter: number;
}

function assertVisit(input: string, expected: VisitorCallback[], expectedErrors: VisitorError[] = [], disallowComments = false): void {
function assertVisit(input: string, expected: VisitorCallback[], expectedErrors: VisitorError[] = [], disallowComments = false, stopOnLine = -1, stopOnChar = -1): void {
let errors: VisitorError[] = [];
let actuals: VisitorCallback[] = [];
let noArgHalder = (id: keyof JSONVisitor) => (offset: number, length: number, startLine: number, startCharacter: number) => actuals.push({ id, text: input.substr(offset, length), startLine, startCharacter });
let noArgHalderWithPath = (id: keyof JSONVisitor) => (offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => actuals.push({ id, text: input.substr(offset, length), startLine, startCharacter, path: pathSupplier() });
let noArgHalderWithPath = (id: keyof JSONVisitor) => (offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => {actuals.push({ id, text: input.substr(offset, length), startLine, startCharacter, path: pathSupplier() });};
let oneArgHalder = (id: keyof JSONVisitor) => (arg: any, offset: number, length: number, startLine: number, startCharacter: number) => actuals.push({ id, text: input.substr(offset, length), startLine, startCharacter, arg });
let oneArgHalderWithPath = (id: keyof JSONVisitor) => (arg: any, offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => actuals.push({ id, text: input.substr(offset, length), startLine, startCharacter, arg, path: pathSupplier() });
let beginHalder = (id: keyof JSONVisitor) => (offset: number, length: number, startLine: number, startCharacter: number, pathSupplier: () => JSONPath) => { actuals.push({ id, text: input.substr(offset, length), startLine, startCharacter, path: pathSupplier() }); if (!(stopOnLine === -1 && stopOnChar === -1)) { if ((stopOnLine === -1 || stopOnLine === startLine) && (stopOnChar === -1 || stopOnChar === startCharacter)) { return false; } } return true; };
visit(input, {
onObjectBegin: noArgHalderWithPath('onObjectBegin'),
onObjectBegin: beginHalder('onObjectBegin'),
onObjectProperty: oneArgHalderWithPath('onObjectProperty'),
onObjectEnd: noArgHalder('onObjectEnd'),
onArrayBegin: noArgHalderWithPath('onArrayBegin'),
onArrayBegin: beginHalder('onArrayBegin'),
onArrayEnd: noArgHalder('onArrayEnd'),
onLiteralValue: oneArgHalderWithPath('onLiteralValue'),
onSeparator: oneArgHalder('onSeparator'),
Expand Down Expand Up @@ -458,6 +459,18 @@ suite('JSON', () => {
{ id: 'onObjectEnd', text: '}', startLine: 0, startCharacter: 20 },
{ id: 'onObjectEnd', text: '}', startLine: 0, startCharacter: 22 },
]);
assertVisit('{ "foo": "bar", "a": {"b": "c"} }', [
{ id: 'onObjectBegin', text: '{', startLine: 0, startCharacter: 0, path: [] },
{ id: 'onObjectEnd', text: '}', startLine: 0, startCharacter: 32 },
], [], false, 0);
assertVisit('{ "a": { "b": "c", "d": { "e": "f" } } }', [
{ id: 'onObjectBegin', text: '{', startLine: 0, startCharacter: 0, path: [] },
{ id: 'onObjectProperty', text: '"a"', startLine: 0, startCharacter: 2, arg: 'a', path: [] },
{ id: 'onSeparator', text: ':', startLine: 0, startCharacter: 5, arg: ':' },
{ id: 'onObjectBegin', text: '{', startLine: 0, startCharacter: 7, path: ['a'] },
{ id: 'onObjectEnd', text: '}', startLine: 0, startCharacter: 37 },
{ id: 'onObjectEnd', text: '}', startLine: 0, startCharacter: 39 }
], [], true, 0, 7);
});

test('visit: array', () => {
Expand Down Expand Up @@ -514,6 +527,14 @@ suite('JSON', () => {
{ id: 'onObjectEnd', text: '}', startLine: 0, startCharacter: 58 },
{ id: 'onArrayEnd', text: ']', startLine: 0, startCharacter: 60 },
]);
assertVisit('{ "foo": [ { "a": "b", "c:": "d", "d": { "e": "f" } } ] }', [
{ id: 'onObjectBegin', text: '{', startLine: 0, startCharacter: 0, path: [] },
{ id: 'onObjectProperty', text: '"foo"', startLine: 0, startCharacter: 2, arg: 'foo', path: [] },
{ id: 'onSeparator', text: ':', startLine: 0, startCharacter: 7, arg: ':' },
{ id: 'onArrayBegin', text: '[', startLine: 0, startCharacter: 9, path: ['foo'] },
{ id: 'onArrayEnd', text: ']', startLine: 0, startCharacter: 54 },
{ id: 'onObjectEnd', text: '}', startLine: 0, startCharacter: 56 }
], [], true, 0, 9);
});

test('visit: comment', () => {
Expand Down