in src/impl/parser.ts [386:645]
export function visit(text: string, visitor: JSONVisitor, options: ParseOptions = ParseOptions.DEFAULT): any {
const _scanner = createScanner(text, false);
// 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 = [];
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;
}
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;
}
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;
}
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;
}
const onObjectBegin = toNoArgVisitWithPath(visitor.onObjectBegin),
onObjectProperty = toOneArgVisitWithPath(visitor.onObjectProperty),
onObjectEnd = toNoArgVisit(visitor.onObjectEnd),
onArrayBegin = toNoArgVisitWithPath(visitor.onArrayBegin),
onArrayEnd = toNoArgVisit(visitor.onArrayEnd),
onLiteralValue = toOneArgVisitWithPath(visitor.onLiteralValue),
onSeparator = toOneArgVisit(visitor.onSeparator),
onComment = toNoArgVisit(visitor.onComment),
onError = toOneArgVisit(visitor.onError);
const disallowComments = options && options.disallowComments;
const allowTrailingComma = options && options.allowTrailingComma;
function scanNext(): SyntaxKind {
while (true) {
const token = _scanner.scan();
switch (_scanner.getTokenError()) {
case ScanError.InvalidUnicode:
handleError(ParseErrorCode.InvalidUnicode);
break;
case ScanError.InvalidEscapeCharacter:
handleError(ParseErrorCode.InvalidEscapeCharacter);
break;
case ScanError.UnexpectedEndOfNumber:
handleError(ParseErrorCode.UnexpectedEndOfNumber);
break;
case ScanError.UnexpectedEndOfComment:
if (!disallowComments) {
handleError(ParseErrorCode.UnexpectedEndOfComment);
}
break;
case ScanError.UnexpectedEndOfString:
handleError(ParseErrorCode.UnexpectedEndOfString);
break;
case ScanError.InvalidCharacter:
handleError(ParseErrorCode.InvalidCharacter);
break;
}
switch (token) {
case SyntaxKind.LineCommentTrivia:
case SyntaxKind.BlockCommentTrivia:
if (disallowComments) {
handleError(ParseErrorCode.InvalidCommentToken);
} else {
onComment();
}
break;
case SyntaxKind.Unknown:
handleError(ParseErrorCode.InvalidSymbol);
break;
case SyntaxKind.Trivia:
case SyntaxKind.LineBreakTrivia:
break;
default:
return token;
}
}
}
function handleError(error: ParseErrorCode, skipUntilAfter: SyntaxKind[] = [], skipUntil: SyntaxKind[] = []): void {
onError(error);
if (skipUntilAfter.length + skipUntil.length > 0) {
let token = _scanner.getToken();
while (token !== SyntaxKind.EOF) {
if (skipUntilAfter.indexOf(token) !== -1) {
scanNext();
break;
} else if (skipUntil.indexOf(token) !== -1) {
break;
}
token = scanNext();
}
}
}
function parseString(isValue: boolean): boolean {
const value = _scanner.getTokenValue();
if (isValue) {
onLiteralValue(value);
} else {
onObjectProperty(value);
// add property name afterwards
_jsonPath.push(value);
}
scanNext();
return true;
}
function parseLiteral(): boolean {
switch (_scanner.getToken()) {
case SyntaxKind.NumericLiteral:
const tokenValue = _scanner.getTokenValue();
let value = Number(tokenValue);
if (isNaN(value)) {
handleError(ParseErrorCode.InvalidNumberFormat);
value = 0;
}
onLiteralValue(value);
break;
case SyntaxKind.NullKeyword:
onLiteralValue(null);
break;
case SyntaxKind.TrueKeyword:
onLiteralValue(true);
break;
case SyntaxKind.FalseKeyword:
onLiteralValue(false);
break;
default:
return false;
}
scanNext();
return true;
}
function parseProperty(): boolean {
if (_scanner.getToken() !== SyntaxKind.StringLiteral) {
handleError(ParseErrorCode.PropertyNameExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]);
return false;
}
parseString(false);
if (_scanner.getToken() === SyntaxKind.ColonToken) {
onSeparator(':');
scanNext(); // consume colon
if (!parseValue()) {
handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]);
}
} else {
handleError(ParseErrorCode.ColonExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]);
}
_jsonPath.pop(); // remove processed property name
return true;
}
function parseObject(): boolean {
onObjectBegin();
scanNext(); // consume open brace
let needsComma = false;
while (_scanner.getToken() !== SyntaxKind.CloseBraceToken && _scanner.getToken() !== SyntaxKind.EOF) {
if (_scanner.getToken() === SyntaxKind.CommaToken) {
if (!needsComma) {
handleError(ParseErrorCode.ValueExpected, [], []);
}
onSeparator(',');
scanNext(); // consume comma
if (_scanner.getToken() === SyntaxKind.CloseBraceToken && allowTrailingComma) {
break;
}
} else if (needsComma) {
handleError(ParseErrorCode.CommaExpected, [], []);
}
if (!parseProperty()) {
handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]);
}
needsComma = true;
}
onObjectEnd();
if (_scanner.getToken() !== SyntaxKind.CloseBraceToken) {
handleError(ParseErrorCode.CloseBraceExpected, [SyntaxKind.CloseBraceToken], []);
} else {
scanNext(); // consume close brace
}
return true;
}
function parseArray(): boolean {
onArrayBegin();
scanNext(); // consume open bracket
let isFirstElement = true;
let needsComma = false;
while (_scanner.getToken() !== SyntaxKind.CloseBracketToken && _scanner.getToken() !== SyntaxKind.EOF) {
if (_scanner.getToken() === SyntaxKind.CommaToken) {
if (!needsComma) {
handleError(ParseErrorCode.ValueExpected, [], []);
}
onSeparator(',');
scanNext(); // consume comma
if (_scanner.getToken() === SyntaxKind.CloseBracketToken && allowTrailingComma) {
break;
}
} else if (needsComma) {
handleError(ParseErrorCode.CommaExpected, [], []);
}
if (isFirstElement) {
_jsonPath.push(0);
isFirstElement = false;
} else {
(_jsonPath[_jsonPath.length - 1] as number)++;
}
if (!parseValue()) {
handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBracketToken, SyntaxKind.CommaToken]);
}
needsComma = true;
}
onArrayEnd();
if (!isFirstElement) {
_jsonPath.pop(); // remove array index
}
if (_scanner.getToken() !== SyntaxKind.CloseBracketToken) {
handleError(ParseErrorCode.CloseBracketExpected, [SyntaxKind.CloseBracketToken], []);
} else {
scanNext(); // consume close bracket
}
return true;
}
function parseValue(): boolean {
switch (_scanner.getToken()) {
case SyntaxKind.OpenBracketToken:
return parseArray();
case SyntaxKind.OpenBraceToken:
return parseObject();
case SyntaxKind.StringLiteral:
return parseString(true);
default:
return parseLiteral();
}
}
scanNext();
if (_scanner.getToken() === SyntaxKind.EOF) {
if (options.allowEmptyContent) {
return true;
}
handleError(ParseErrorCode.ValueExpected, [], []);
return false;
}
if (!parseValue()) {
handleError(ParseErrorCode.ValueExpected, [], []);
return false;
}
if (_scanner.getToken() !== SyntaxKind.EOF) {
handleError(ParseErrorCode.EndOfFileExpected, [], []);
}
return true;
}