import { Json } from "../../../extension.bundle" ; import { assert } from "../../fixed_assert" ; import { assertNever } from "../../util/assertNever" ; import { CachedValue } from "../../util/CachedValue" ; import { CaseInsensitiveMap } from "../../util/CaseInsensitiveMap" ; import { Iterator } from "../../util/Iterator" ; import { nonNullValue } from "../../util/nonNull" ; import * as utilities from "../../util/strings" ; import { LineColPos } from "../LineColPos" ; import { ContainsBehavior , Span } from "../Span" ; import * as basic from "./Tokenizer" ; export enum ValueKind { 'ObjectValue' , 'StringValue' , 'PropertyValue' , 'ArrayValue' , 'NumberValue' , 'NullValue' , 'BooleanValue' } /**
 * The different types of tokens that can be parsed from a JSON string.
 */ export enum TokenType { LeftCurlyBracket = 0 , RightCurlyBracket = 1 , LeftSquareBracket = 2 , RightSquareBracket = 3 , Comma = 4 , Colon = 5 , Whitespace = 6 , QuotedString = 7 , Number = 8 , Boolean = 9 , Literal = 10 , Null = 11 , Comment = 12 , Unrecognized = 13 } export enum Comments { /**
     * Default (parser generally ignores comments)
     */ ignoreCommentTokens , includeCommentTokens } /**
 * An individual segment of a JSON document. This could be a single character (such as ':') or an
 * entire structure (such as '{}').
 */ export abstract class Segment { constructor ( private _startIndex ) { } /**
     * Get the number of characters that make up this Segment.
     */ public abstract length ( ) ; /**
     * Get the span for this Segment.
     */ public get span ( ) : Span { return new Span ( this . _startIndex , this . length ( ) ) ; } /**
     * Get the original string that this Segment was parsed from.
     */ public abstract toString ( ) ; } /**
 * A JSON token from a JSON string. The JSON token is made up of one or more basic tokens (for instance,
 * a JSON string literal would be composed of the basic token representing a double quote, followed
 * by some group of basic tokens representing groups of letters, groups of numbers, punctuation, etc.,
 * and possibly ending with a double quote basic token).
 */ export class Token extends Segment { constructor ( private _type , _startIndex , private _basicTokens ) { super ( _startIndex ) ; } public get type ( ) : TokenType { return this . _type ; } public length ( ) { return utilities . getCombinedLength ( this . _basicTokens ) ; } /**
     * Gets the original string that this token was parsed from.
     */ public toString ( ) { return utilities . getCombinedText ( this . _basicTokens ) ; } /**
     * Convenient way of seeing what this token represents in the debugger, shouldn't be used for production code
     */ public get __debugDisplay ( ) : string { return this . toString ( ) ; } } export function LeftCurlyBracket ( startIndex ) { return new Token ( TokenType . LeftCurlyBracket , startIndex , [ basic . LeftCurlyBracket ] ) ; } export function RightCurlyBracket ( startIndex ) { return new Token ( TokenType . RightCurlyBracket , startIndex , [ basic . RightCurlyBracket ] ) ; } export function LeftSquareBracket ( startIndex ) { return new Token ( TokenType . LeftSquareBracket , startIndex , [ basic . LeftSquareBracket ] ) ; } export function RightSquareBracket ( startIndex ) { return new Token ( TokenType . RightSquareBracket , startIndex , [ basic . RightSquareBracket ] ) ; } export function Comma ( startIndex ) { return new Token ( TokenType . Comma , startIndex , [ basic . Comma ] ) ; } export function Colon ( startIndex ) { return new Token ( TokenType . Colon , startIndex , [ basic . Colon ] ) ; } export function Whitespace ( startIndex , basicTokens ) { return new Token ( TokenType . Whitespace , startIndex , basicTokens ) ; } export function QuotedString ( startIndex , basicTokens ) { return new Token ( TokenType . QuotedString , startIndex , basicTokens ) ; } export function Number ( startIndex , basicTokens ) { return new Token ( TokenType . Number , startIndex , basicTokens ) ; } export function Boolean ( startIndex , basicToken ) { return new Token ( TokenType . Boolean , startIndex , [ basicToken ] ) ; } export function Literal ( startIndex , basicTokens ) { return new Token ( TokenType . Literal , startIndex , basicTokens ) ; } export function Null ( startIndex ) { return new Token ( TokenType . Null , startIndex , [ basic . Letters ( "null" ) ] ) ; } export function Comment ( startIndex , basicTokens ) { return new Token ( TokenType . Comment , startIndex , basicTokens ) ; } export function Unrecognized ( startIndex , basicToken ) { return new Token ( TokenType . Unrecognized , startIndex , [ basicToken ] ) ; } export function asObjectValue ( value ) { return value instanceof ObjectValue ? value : undefined ; } export function asArrayValue ( value ) { return value instanceof ArrayValue ? value : undefined ; } export function asStringValue ( value ) { return value instanceof StringValue ? value : undefined ; } export function asNumberValue ( value ) { return value instanceof NumberValue ? value : undefined ; } export function asBooleanValue ( value ) { return value instanceof BooleanValue ? value : undefined ; } /**
 * Read a JSON quoted string from the provided tokenizer. The tokenizer must be pointing at
 * either a SingleQuote or DoubleQuote Token.
 *
 * Note that the returned quoted string may not end with a quote (if EOD is reached)
 */ export function readQuotedString ( iterator ) { const startingToken = iterator . current ( ) ; assert ( startingToken && ( startingToken . getType ( ) === basic . TokenType . SingleQuote || startingToken . getType ( ) === basic . TokenType . DoubleQuote ) ) ; const startQuote = startingToken ! ; const quotedStringTokens = [ startQuote ] ; iterator . moveNext ( ) ; let escaped = false ; let endQuote ; let current = iterator . current ( ) ; while ( ! endQuote && current ) { quotedStringTokens . push ( current ) ; if ( escaped ) { escaped = false ; } else { if ( current . getType ( ) === basic . TokenType . Backslash ) { escaped = true ; } else if ( current . getType ( ) === startQuote . getType ( ) ) { endQuote = iterator . current ( ) ; } } iterator . moveNext ( ) ; current = iterator . current ( ) ; } return quotedStringTokens ; } /**
 * Read a JSON whitespace string from the provided tokenizer.
 */ export function readWhitespace ( iterator ) { const whitespaceTokens = [ ] ; while ( true ) { let current = iterator . current ( ) ; if ( ! current ) { return whitespaceTokens ; } switch ( current . getType ( ) ) { case basic . TokenType . Space : case basic . TokenType . Tab : case basic . TokenType . CarriageReturn : case basic . TokenType . NewLine : case basic . TokenType . CarriageReturnNewLine : whitespaceTokens . push ( current ) ; iterator . moveNext ( ) ; current = iterator . current ( ) ; break ; default : return whitespaceTokens ; } } } /**
 * Read a JSON number from the provided iterator. The iterator must be pointing at either a
 * Dash or Digits Token when this function is called.
 */ export function readNumber ( iterator ) { const numberBasicTokens = [ ] ; const dashOrDigitsToken = iterator . current ( ) ! ; if ( dashOrDigitsToken . getType ( ) === basic . TokenType . Dash ) { numberBasicTokens . push ( dashOrDigitsToken ) ; iterator . moveNext ( ) ; } const digits = iterator . current ( ) ; if ( digits && digits . getType ( ) === basic . TokenType . Digits ) { numberBasicTokens . push ( digits ) ; iterator . moveNext ( ) ; } const decimal = iterator . current ( ) ; if ( decimal && decimal . getType ( ) === basic . TokenType . Period ) { numberBasicTokens . push ( decimal ) ; iterator . moveNext ( ) ; const fractionalDigits = iterator . current ( ) ; if ( fractionalDigits && fractionalDigits . getType ( ) === basic . TokenType . Digits ) { numberBasicTokens . push ( fractionalDigits ) ; iterator . moveNext ( ) ; } } const exponentLetter = iterator . current ( ) ; if ( exponentLetter ) { if ( exponentLetter . getType ( ) === basic . TokenType . Letters && exponentLetter . toString ( ) . toLowerCase ( ) === "e" ) { numberBasicTokens . push ( exponentLetter ) ; iterator . moveNext ( ) ; const exponentSign = iterator . current ( ) ; if ( exponentSign && ( exponentSign . getType ( ) === basic . TokenType . Dash || exponentSign . getType ( ) === basic . TokenType . Plus ) ) { numberBasicTokens . push ( exponentSign ) ; iterator . moveNext ( ) ; } const exponentDigits = iterator . current ( ) ; if ( exponentDigits && exponentDigits . getType ( ) === basic . TokenType . Digits ) { numberBasicTokens . push ( exponentDigits ) ; iterator . moveNext ( ) ; } } } return numberBasicTokens ; } /**
 * A JSON tokenizer that tokenizes JSON strings.
 */ export class Tokenizer { private _innerTokenizer : basic . Tokenizer ; private _current : Token | undefined ; private _currentTokenStartIndex : number ; private _lineLengths : number [ ] = [ 0 ] ; private _commentsCount : number = 0 ; constructor ( jsonDocumentText , startIndex = 0 ) { this . _innerTokenizer = new basic . Tokenizer ( jsonDocumentText ) ; this . _currentTokenStartIndex = startIndex ; } public hasStarted ( ) { return this . _innerTokenizer . hasStarted ( ) ; } public get current ( ) : Token | undefined { return this . _current ; } public get lineLengths ( ) : number [ ] { return this . _lineLengths ; } public get commentsCount ( ) : number { return this . _commentsCount ; } /**
     * Get the current basic token that the basic Tokenizer is pointing at.
     */ private currentBasicToken ( ) { return this . _innerTokenizer . current ( ) ; } private currentBasicTokenType ( ) { const currentBasicToken = this . currentBasicToken ( ) ; return currentBasicToken ? currentBasicToken . getType ( ) : undefined ; } returns private moveNextBasicToken ( ) { const result = this . _innerTokenizer . moveNext ( ) ; let currentBasicToken = this . currentBasicToken ( ) ; if ( currentBasicToken ) { let currentBasicTokenType = this . currentBasicTokenType ( ) ; this . _lineLengths [ this . _lineLengths . length - 1 ] += currentBasicToken . length ( ) ; if ( currentBasicTokenType === basic . TokenType . NewLine || currentBasicTokenType === basic . TokenType . CarriageReturnNewLine ) { this . _lineLengths . push ( 0 ) ; } } return result ; } private asBasicTokenIterator ( ) { return { hasStarted : ( ) : boolean => { return this . hasStarted ( ) ; } , current : ( ) : basic . Token | undefined => { return this . currentBasicToken ( ) ; } , moveNext : ( ) : boolean => { return this . moveNextBasicToken ( ) ; } } ; } public moveNext ( ) { if ( ! this . hasStarted ( ) ) { this . moveNextBasicToken ( ) ; } else if ( this . current ) { this . _currentTokenStartIndex += this . current . length ( ) ; } this . _current = undefined ; if ( this . currentBasicToken ( ) ) { switch ( this . currentBasicTokenType ( ) ) { case basic . TokenType . LeftCurlyBracket : assert . deepStrictEqual ( this . currentBasicTokenType ( ) , basic . TokenType . LeftCurlyBracket ) ; this . _current = LeftCurlyBracket ( this . _currentTokenStartIndex ) ; this . moveNextBasicToken ( ) ; break ; case basic . TokenType . RightCurlyBracket : this . _current = RightCurlyBracket ( this . _currentTokenStartIndex ) ; this . moveNextBasicToken ( ) ; break ; case basic . TokenType . LeftSquareBracket : this . _current = LeftSquareBracket ( this . _currentTokenStartIndex ) ; this . moveNextBasicToken ( ) ; break ; case basic . TokenType . RightSquareBracket : this . _current = RightSquareBracket ( this . _currentTokenStartIndex ) ; this . moveNextBasicToken ( ) ; break ; case basic . TokenType . Comma : this . _current = Comma ( this . _currentTokenStartIndex ) ; this . moveNextBasicToken ( ) ; break ; case basic . TokenType . Colon : this . _current = Colon ( this . _currentTokenStartIndex ) ; this . moveNextBasicToken ( ) ; break ; case basic . TokenType . Dash : case basic . TokenType . Digits : this . _current = Number ( this . _currentTokenStartIndex , readNumber ( this . asBasicTokenIterator ( ) ) ) ; break ; case basic . TokenType . ForwardSlash : this . moveNextBasicToken ( ) ; if ( ! this . currentBasicToken ( ) ) { this . _current = Literal ( this . _currentTokenStartIndex , [ basic . ForwardSlash ] ) ; } else { switch ( this . currentBasicTokenType ( ) ) { case basic . TokenType . ForwardSlash : const lineCommentBasicTokens = [ basic . ForwardSlash , basic . ForwardSlash ] ; while ( this . moveNextBasicToken ( ) && this . currentBasicTokenType ( ) !== basic . TokenType . NewLine && this . currentBasicTokenType ( ) !== basic . TokenType . CarriageReturnNewLine ) { lineCommentBasicTokens . push ( this . currentBasicToken ( ) ! ) ; } this . _current = Comment ( this . _currentTokenStartIndex , lineCommentBasicTokens ) ; this . _commentsCount ++ ; break ; case basic . TokenType . Asterisk : const blockCommentBasicTokens = [ basic . ForwardSlash , basic . Asterisk ] ; this . moveNextBasicToken ( ) ; while ( this . currentBasicToken ( ) ) { blockCommentBasicTokens . push ( this . currentBasicToken ( ) ! ) ; if ( this . currentBasicTokenType ( ) === basic . TokenType . Asterisk ) { this . moveNextBasicToken ( ) ; if ( this . currentBasicTokenType ( ) === basic . TokenType . ForwardSlash ) { blockCommentBasicTokens . push ( this . currentBasicToken ( ) ! ) ; this . moveNextBasicToken ( ) ; break ; } } else { this . moveNextBasicToken ( ) ; } } this . _current = Comment ( this . _currentTokenStartIndex , blockCommentBasicTokens ) ; this . _commentsCount ++ ; break ; default : this . _current = Literal ( this . _currentTokenStartIndex , [ basic . ForwardSlash ] ) ; break ; } } break ; case basic . TokenType . Space : case basic . TokenType . Tab : case basic . TokenType . CarriageReturn : case basic . TokenType . NewLine : case basic . TokenType . CarriageReturnNewLine : this . _current = Whitespace ( this . _currentTokenStartIndex , readWhitespace ( this . asBasicTokenIterator ( ) ) ) ; break ; case basic . TokenType . SingleQuote : case basic . TokenType . DoubleQuote : this . _current = QuotedString ( this . _currentTokenStartIndex , readQuotedString ( this . asBasicTokenIterator ( ) ) ) ; break ; case basic . TokenType . Letters : switch ( this . currentBasicToken ( ) ! . toString ( ) ) { case "true" : case "false" : this . _current = Boolean ( this . _currentTokenStartIndex , this . currentBasicToken ( ) ! ) ; this . moveNextBasicToken ( ) ; break ; case "null" : this . _current = Null ( this . _currentTokenStartIndex ) ; this . moveNextBasicToken ( ) ; break ; default : const literalTokens = [ this . currentBasicToken ( ) ! ] ; this . moveNextBasicToken ( ) ; while ( this . currentBasicToken ( ) && ( this . currentBasicTokenType ( ) === basic . TokenType . Letters || this . currentBasicTokenType ( ) === basic . TokenType . Underscore ) ) { literalTokens . push ( this . currentBasicToken ( ) ! ) ; this . moveNextBasicToken ( ) ; } this . _current = Literal ( this . _currentTokenStartIndex , literalTokens ) ; break ; } break ; default : this . _current = Unrecognized ( this . _currentTokenStartIndex , this . currentBasicToken ( ) ! ) ; this . moveNextBasicToken ( ) ; break ; } } return ! ! this . current ; } } /**
 * The Value class is the generic JSON value base class that all other JSON value types inherit
 * from.
 */ export abstract class Value { constructor ( private _span ) { } public static toFullFriendlyString ( value ) { return value === undefined ? 'undefined' : value . toFullFriendlyString ( ) ; } public abstract get valueKind ( ) : ValueKind ; /**
     * The span that this Value covers (character start index through after the character end index).
     */ public get span ( ) : Span { return this . _span ; } public get startIndex ( ) : number { return this . _span . startIndex ; } public get length ( ) : number { return this . _span . length ; } public abstract accept ( visitor ) ; /**
     * A short version of a user-friendly string that represents this value, suitable for display in a label, etc.
     * For example, displays (object) instead of the full object value.
     */ public abstract toShortFriendlyString ( ) ; /**
     * A full version of a user-friendly string that represents this value, suitable for display in a label, etc.
     */ public abstract toFullFriendlyString ( ) ; /**
     * Convenient way of seeing what this object represents in the debugger, shouldn't be used for production code
     */ public get __debugDisplay ( ) : string { return this . toFullFriendlyString ( ) ; } public get propertyName ( ) : string | undefined { return this . asPropertyValue ?. nameValue . asStringValue ?. unquotedValue ; } public isPropertyWithName ( propertyNameCaseInsensitive ) { return this . propertyName ?. toLowerCase ( ) === propertyNameCaseInsensitive . toLowerCase ( ) ; } public get asObjectValue ( ) : ObjectValue | undefined { return asObjectValue ( this ) ; } public get asArrayValue ( ) : ArrayValue | undefined { return asArrayValue ( this ) ; } public get asStringValue ( ) : StringValue | undefined { return asStringValue ( this ) ; } public get asNumberValue ( ) : NumberValue | undefined { return asNumberValue ( this ) ; } public get asBooleanValue ( ) : BooleanValue | undefined { return asBooleanValue ( this ) ; } public get asPropertyValue ( ) : Property | undefined { return this instanceof Property ? this : undefined ; } /**
     * Retrieves the enlosing objects, arrays and properties of the given descendent, in order of top-most to deepest
     */ public findLineage ( descendent ) { return FindLineageVisitor . visit ( this , descendent ) ; } } /**
 * A JSON object that contains properties.
 */ export class ObjectValue extends Value { private _caseInsensitivePropertyMap : CachedValue < CaseInsensitiveMap < string , Property | undefined > > = new CachedValue < CaseInsensitiveMap < string , Property | undefined > > ( ) ; constructor ( span , private _properties ) { super ( span ) ; assert ( this . _properties ) ; } public get valueKind ( ) : ValueKind { return ValueKind . ObjectValue ; } /**
     * Get the map of property names to properties for this ObjectValue. This mapping is
     * created lazily.
     */ private get caseInsensitivePropertyMap ( ) : CaseInsensitiveMap < string , Property | undefined > { return this . _caseInsensitivePropertyMap . getOrCacheValue ( ( ) => { const caseInsensitivePropertyMap = new CaseInsensitiveMap < string , Property | undefined > ( ) ; if ( this . _properties . length > 0 ) { for ( const property of this . _properties ) { caseInsensitivePropertyMap . set ( property . nameValue . toString ( ) , property ) ; } } return caseInsensitivePropertyMap ; } ) ; } public get properties ( ) : Property [ ] { return this . _properties ; } /**
     * Get whether a property with the provided propertyName is defined or not on this ObjectValue.
     */ public hasProperty ( propertyName ) { return ! ! this . getPropertyValue ( propertyName ) ; } /**
     * Get the property value for the provided property name. If no property exists with the
     * provided name (case-insensitive), then undefined will be returned.
     */ public getPropertyValue ( propertyName ) { const result = this . caseInsensitivePropertyMap . get ( propertyName ) ; return result ? result . value : undefined ; } /**
     * Get the property for the provided property name. If no property exists with the
     * provided name (case-insensitive), then undefined will be returned.
     */ public getProperty ( propertyName ) { const result = this . caseInsensitivePropertyMap . get ( propertyName ) ; return result ? result : undefined ; } /**
     * Get the property value that is at the chain of properties in the provided property name
     * stack. If the provided property name stack is empty, then return this value.
     */ public getPropertyValueFromStack ( propertyNameStack ) { let result = < Value | undefined > this ; while ( result && propertyNameStack . length > 0 ) { const objectValue = asObjectValue ( result ) ; if ( objectValue ) { const propertyName = propertyNameStack . pop ( ) ; result = propertyName ? objectValue . getPropertyValue ( propertyName ) : undefined ; } else { return undefined ; } } return result ; } /**
     * Get the property names
     */ public get propertyNames ( ) : string [ ] { return [ ... this . caseInsensitivePropertyMap . keys ( ) ] ; } public accept ( visitor ) { visitor . visitObjectValue ( this ) ; } public toShortFriendlyString ( ) { return "(object)" ; } public toFullFriendlyString ( ) { return "{" + this . properties . map ( pv => `"${ pv . nameValue . toString ( ) }": ${ Value . toFullFriendlyString ( pv . value ) }` ) . join ( ", " ) + "}" ; } } /**
 * A property in a JSON ObjectValue.
 */ export class Property extends Value { constructor ( span , private _name , private _value ) { super ( span ) ; } public get valueKind ( ) : ValueKind { return ValueKind . PropertyValue ; } /**
     * The name of the property.
     */ public get nameValue ( ) : StringValue { return this . _name ; } /**
     * The value of the property.
     */ public get value ( ) : Value | undefined { return this . _value ; } public accept ( visitor ) { visitor . visitProperty ( this ) ; } public toShortFriendlyString ( ) { return "(property)" ; } public toFullFriendlyString ( ) { return this . _name . quotedValue + ": " + Value . toFullFriendlyString ( this . value ) ; } } /**
 * The properties that are assigned to an Object.
 */ export interface Properties { [ key ] : Value ; } /**
 * A JSON array that contains elements.
 */ export class ArrayValue extends Value { constructor ( span , private _elements ) { super ( span ) ; assert ( _elements ) ; } public get valueKind ( ) : ValueKind { return ValueKind . ArrayValue ; } /**
     * The elements contained in this Array.
     */ public get elements ( ) : Value [ ] { return this . _elements ; } /**
     * The number of elements in this Array.
     */ public get length ( ) : number { return this . _elements . length ; } public accept ( visitor ) { visitor . visitArrayValue ( this ) ; } public toShortFriendlyString ( ) { return "(array)" ; } public toFullFriendlyString ( ) { return "[" + this . elements . map ( e => e . toFullFriendlyString ( ) ) . join ( ", " ) + "]" ; } } /**
 * A JSON boolean that can be either true or false.
 */ export class BooleanValue extends Value { constructor ( span , private _value ) { super ( span ) ; } public get valueKind ( ) : ValueKind { return ValueKind . BooleanValue ; } /**
     * The boolean value of this JSON Boolean.
     */ public toBoolean ( ) { return this . _value ; } public toString ( ) { return this . _value . toString ( ) ; } public accept ( visitor ) { visitor . visitBooleanValue ( this ) ; } public toShortFriendlyString ( ) { return this . toString ( ) ; } public toFullFriendlyString ( ) { return this . toString ( ) ; } } /**
 * A JSON quoted string.
 */ export class StringValue extends Value { private _unquotedValue : string ; constructor ( quotedSpan , private _quotedValue ) { super ( quotedSpan ) ; this . _unquotedValue = utilities . unquote ( _quotedValue ) ; } public get valueKind ( ) : ValueKind { return ValueKind . StringValue ; } public get unquotedSpan ( ) : Span { return new Span ( this . startIndex + 1 , this . _unquotedValue . length ) ; } public get unquotedValue ( ) : string { return this . _unquotedValue ; } public get quotedValue ( ) : string { return this . _quotedValue ; } public toString ( ) { return this . _unquotedValue ; } public accept ( visitor ) { visitor . visitStringValue ( this ) ; } public toShortFriendlyString ( ) { return this . unquotedValue ; } public toFullFriendlyString ( ) { return this . quotedValue ; } } /**
 * A JSON number.
 */ export class NumberValue extends Value { constructor ( span , private _text ) { super ( span ) ; } public get valueKind ( ) : ValueKind { return ValueKind . NumberValue ; } public toString ( ) { return this . _text . toString ( ) ; } public accept ( visitor ) { visitor . visitNumberValue ( this ) ; } public toShortFriendlyString ( ) { return this . toString ( ) ; } public toFullFriendlyString ( ) { return this . toString ( ) ; } } /**
 * A JSON null value.
 */ export class NullValue extends Value { constructor ( span ) { super ( span ) ; } public get valueKind ( ) : ValueKind { return ValueKind . NullValue ; } public toString ( ) { return "null" ; } public accept ( visitor ) { visitor . visitNullValue ( this ) ; } public toShortFriendlyString ( ) { return this . toString ( ) ; } public toFullFriendlyString ( ) { return this . toString ( ) ; } } /**
 * The result of parsing a JSON string.
 */ export class ParseResult { private readonly _debugText : string ; constructor ( private _tokens , private _commentTokens , private _lineLengths , private _value , text , public readonly commentCount ) { nonNullValue ( _tokens , "_tokens" ) ; nonNullValue ( _lineLengths , "_lineLengths" ) ; this . _debugText = text ; this . _debugText = this . _debugText ; } public get tokenCount ( ) : number { return this . _tokens . length ; } public get tokens ( ) : Token [ ] { return this . _tokens ; } public get commentTokens ( ) : Token [ ] { return this . _commentTokens ; } public get commentTokenCount ( ) : number { return this . _commentTokens . length ; } /**
     * Each line's length includes the terminating CR or CR/LF, if present, except for
     * the last line, which doesn't.
     * This means:
     *   - If the last character in the JSON is an LF or CR/LF, there is one line after
     *   that of length 0
     *   - If the text is empty, there is one line of length 0
     *   - Line length is affected by whether it' terminated by LF or CR/LF
     *
     * CONSIDER: redesign
     */ public get lineLengths ( ) : number [ ] { return this . _lineLengths ; } public getTokens ( commentBehavior ) { if ( commentBehavior === Comments . includeCommentTokens ) { const tokens = this . tokens . concat ( this . commentTokens ) ; tokens . sort ( ( a , b ) => a . span . startIndex - b . span . startIndex ) ; return tokens ; } else { return this . tokens ; } } public getTokensInSpan ( span , commentsBehavior ) { const results = [ ] ; const tokens = this . getTokens ( commentsBehavior ) ; const spanStartIndex = span . startIndex ; const spanEndIndex = span . endIndex ; for ( let token of tokens ) { if ( token . span . endIndex >= spanStartIndex ) { if ( token . span . startIndex > spanEndIndex ) { break ; } results . push ( token ) ; } } return results ; } public getLastTokenOnLine ( line , commentBehavior = Comments . ignoreCommentTokens ) { const startOfLineIndex = this . getCharacterIndex ( line , 0 ) ; const lastLine = this . lineLengths . length - 1 ; const tokens = this . getTokens ( commentBehavior ) ; let lastSeenToken ; if ( line === lastLine ) { lastSeenToken = tokens [ tokens . length - 1 ] ; } else { const nextLineIndex = this . getCharacterIndex ( line + 1 , 0 ) ; for ( let token of tokens ) { if ( token . span . startIndex >= nextLineIndex ) { break ; } lastSeenToken = token ; } } if ( lastSeenToken && lastSeenToken . span . endIndex >= startOfLineIndex ) { return lastSeenToken ; } else { return undefined ; } } /**
     * Get the highest character index of any line in this JSON parse result.
     */ public get maxCharacterIndex ( ) : number { let result = 0 ; for ( let lineLength of this . _lineLengths ) { result += lineLength ; } return result ; } public get value ( ) : Value | undefined { return this . _value ; } /**
     * Get the character index from the stream's perspective from the provided
     * line and column indexes.
     */ public getCharacterIndex ( lineIndex , columnIndex , options ? ) { assert ( 0 <= lineIndex , `Cannot get a character index for a negative line index (${ lineIndex }).` ) ; if ( lineIndex >= this . lineLengths . length ) { if ( options ?. allowOutOfBounds ) { lineIndex = this . lineLengths . length - 1 ; assert ( lineIndex >= 0 , "lineIndex>=0" ) ; } else { assert . fail ( options ?. allowOutOfBounds , `Cannot get a character index for a line index greater than the number of parsed lines (lineIndex: ${ lineIndex }, lines parsed: ${ this . lineLengths . length }).` ) ; } } assert ( 0 <= columnIndex , `Cannot get a character index for a negative columnIndex (${ columnIndex }).` ) ; const maxColumnIndex = this . getMaxColumnIndex ( lineIndex ) ; if ( columnIndex > maxColumnIndex ) { if ( options ?. allowOutOfBounds ) { columnIndex = Math . max ( 0 , maxColumnIndex - 1 ) ; assert ( maxColumnIndex >= 0 ) ; } else { assert . fail ( `Cannot get a character index for a columnIndex (${ columnIndex }) that is greater than the lineIndex's (${ lineIndex }) line max column index (${ this . getMaxColumnIndex ( lineIndex ) }).` ) ; } } let characterIndex = columnIndex ; for ( let i = 0 ; i < lineIndex ; ++ i ) { characterIndex += this . lineLengths [ i ] ; } assert ( 0 <= characterIndex ) ; return characterIndex ; } public getPositionFromCharacterIndex ( characterIndex ) { assert ( 0 <= characterIndex ) ; let line = 0 ; let column = 0 ; let remainingChars = characterIndex ; for ( let lineLength of this . lineLengths ) { if ( lineLength <= remainingChars ) { ++ line ; remainingChars -= lineLength ; } else { column = remainingChars ; break ; } } if ( line >= this . lineLengths . length ) { line = this . lineLengths . length - 1 ; column = this . lineLengths [ line ] ; } return new LineColPos ( line , column ) ; } public getMaxColumnIndex ( lineIndex ) { assert ( 0 <= lineIndex && lineIndex < this . lineLengths . length ) ; let maxColumnIndex = this . lineLengths [ lineIndex ] ; if ( lineIndex < this . lineLengths . length - 1 ) { -- maxColumnIndex ; } return maxColumnIndex ; } private static getToken ( tokens , tokenIndex ) { assert ( 0 <= tokenIndex && tokenIndex < tokens . length , `The tokenIndex (${ tokenIndex }) must always be between 0 and the token count - 1 (${ tokens . length - 1 }).` ) ; return tokens [ tokenIndex ] ; } private static getLastToken ( tokens ) { return tokens [ tokens . length - 1 ] ; } public getCommentTokenAtDocumentIndex ( characterIndex , containsBehavior ) { const token = this . getTokenAtCharacterIndex ( characterIndex , Comments . includeCommentTokens ) ; if ( token ?. type === TokenType . Comment ) { switch ( containsBehavior ) { case ContainsBehavior . strict : return token ; case ContainsBehavior . extended : assert . fail ( "ContainsBehavior.extended not implemented here (somewhat unambiguous and not clear it's useful)" ) ; case ContainsBehavior . enclosed : if ( token . span . startIndex === characterIndex ) { return undefined ; } return token ; default : assertNever ( containsBehavior ) ; } } const line = this . getPositionFromCharacterIndex ( characterIndex ) . line ; const lastTokenOnLineIncludingComments = this . getLastTokenOnLine ( line , Comments . includeCommentTokens ) ; if ( lastTokenOnLineIncludingComments && lastTokenOnLineIncludingComments . type === TokenType . Comment && lastTokenOnLineIncludingComments . toString ( ) . startsWith ( '//' ) && lastTokenOnLineIncludingComments . span . startIndex < characterIndex ) { return lastTokenOnLineIncludingComments ; } return undefined ; } /**
     * Get the JSON Token that contains the provided characterIndex
     * if any (returns undefined if at whitespace or comment)
     */ public getTokenAtCharacterIndex ( characterIndex , commentBehavior = Comments . includeCommentTokens ) { assert ( 0 <= characterIndex , `characterIndex (${ characterIndex }) cannot be negative.` ) ; const tokens = this . getTokens ( commentBehavior ) ; return ParseResult . getTokenAtCharacterIndex ( tokens , characterIndex ) ; } private static getTokenAtCharacterIndex ( tokens , characterIndex ) { assert ( 0 <= characterIndex , `characterIndex (${ characterIndex }) cannot be negative.` ) ; const tokenCount = tokens . length ; const lastToken = ParseResult . getLastToken ( tokens ) ; let token ; if ( ! ! lastToken && lastToken . span . afterEndIndex === characterIndex ) { token = lastToken ; } else { let minTokenIndex = 0 ; let maxTokenIndex = tokenCount - 1 ; while ( ! token && minTokenIndex <= maxTokenIndex ) { let midTokenIndex = Math . floor ( ( maxTokenIndex + minTokenIndex ) / 2 ) ; let currentToken = ParseResult . getToken ( tokens , midTokenIndex ) ; let currentTokenSpan = currentToken . span ; if ( characterIndex < currentTokenSpan . startIndex ) { maxTokenIndex = midTokenIndex - 1 ; } else if ( currentTokenSpan . endIndex < characterIndex ) { minTokenIndex = midTokenIndex + 1 ; } else { token = currentToken ; } } } return token ; } public getValueAtCharacterIndex ( characterIndex , containsBehavior ) { assert ( 0 <= characterIndex , `characterIndex (${ characterIndex }) cannot be negative.` ) ; let result ; if ( this . value && this . value . span . contains ( characterIndex , containsBehavior ) ) { let current = this . value ; while ( ! result ) { const currentValue = current ; if ( currentValue instanceof Property ) { if ( currentValue . nameValue . span . contains ( characterIndex , containsBehavior ) ) { current = currentValue . nameValue ; } else if ( currentValue . value && currentValue . value . span . contains ( characterIndex , containsBehavior ) ) { current = currentValue . value ; } } else if ( currentValue instanceof ObjectValue ) { assert ( currentValue . properties ) ; for ( const property of currentValue . properties ) { assert ( property ) ; if ( property . span . contains ( characterIndex , containsBehavior ) ) { current = property ; } } } else if ( currentValue instanceof ArrayValue ) { assert ( currentValue . elements ) ; for ( const element of currentValue . elements ) { assert ( element ) ; if ( element . span . contains ( characterIndex , containsBehavior ) ) { current = element ; } } } if ( current === currentValue ) { result = current ; } } } return result ; } } /**
 * Parse the provided JSON string.
 */ export function parse ( stringValue ) { nonNullValue ( stringValue , "stringValue" ) ; const tokens = [ ] ; const commentTokens = [ ] ; const jt = new Tokenizer ( stringValue ) ; const value = parseValue ( jt , tokens , commentTokens ) ; while ( jt . current ) { next ( jt , tokens , commentTokens ) ; } return new ParseResult ( tokens , commentTokens , jt . lineLengths , value , stringValue , jt . commentsCount ) ; } /**
 * Read a JSON object from the provided tokenizer's stream of Tokens.
 * All of the Tokens that are read will be placed into the provided
 * tokens arrays.
 */ function parseValue ( tokenizer , tokens , commentTokens ) { let value ; if ( ! tokenizer . hasStarted ( ) ) { next ( tokenizer , tokens , commentTokens ) ; } if ( tokenizer . current ) { switch ( tokenizer . current . type ) { case TokenType . QuotedString : value = new StringValue ( tokenizer . current . span , tokenizer . current . toString ( ) ) ; next ( tokenizer , tokens , commentTokens ) ; break ; case TokenType . Number : value = new NumberValue ( tokenizer . current . span , tokenizer . current . toString ( ) ) ; next ( tokenizer , tokens , commentTokens ) ; break ; case TokenType . Boolean : value = new BooleanValue ( tokenizer . current . span , tokenizer . current . toString ( ) === "true" ) ; next ( tokenizer , tokens , commentTokens ) ; break ; case TokenType . LeftCurlyBracket : value = parseObject ( tokenizer , tokens , commentTokens ) ; break ; case TokenType . LeftSquareBracket : value = parseArray ( tokenizer , tokens , commentTokens ) ; break ; case TokenType . Null : value = new NullValue ( tokenizer . current . span ) ; next ( tokenizer , tokens , commentTokens ) ; break ; } } return value ; } function parseObject ( tokenizer , tokens , commentTokens ) { if ( ! tokenizer . current ) { throw new Error ( "Precondition failed" ) ; } let objectSpan = tokenizer . current . span ; const properties = [ ] ; next ( tokenizer , tokens , commentTokens ) ; let propertySpan ; let propertyName ; let foundColon = false ; while ( tokenizer . current ) { objectSpan = objectSpan . union ( tokenizer . current . span ) ; if ( tokenizer . current . type === TokenType . RightCurlyBracket ) { next ( tokenizer , tokens , commentTokens ) ; break ; } else if ( ! propertyName ) { if ( tokenizer . current . type === TokenType . QuotedString ) { propertySpan = tokenizer . current . span ; propertyName = new StringValue ( propertySpan , tokenizer . current . toString ( ) ) ; next ( tokenizer , tokens , commentTokens ) ; } else { next ( tokenizer , tokens , commentTokens ) ; } } else if ( ! foundColon ) { propertySpan = propertySpan ? propertySpan . union ( tokenizer . current . span ) : tokenizer . current . span ; if ( tokenizer . current . type === TokenType . Colon ) { foundColon = true ; next ( tokenizer , tokens , commentTokens ) ; } else { propertyName = undefined ; } } else { assert ( foundColon ) ; assert ( propertyName ) ; const propertyValue = parseValue ( tokenizer , tokens , commentTokens ) ; if ( propertyValue ) { propertySpan = propertySpan ? propertySpan . union ( propertyValue . span ) : propertyValue . span ; objectSpan = objectSpan . union ( propertyValue . span ) ; } properties . push ( new Property ( propertySpan || objectSpan , propertyName ! , propertyValue ) ) ; propertySpan = undefined ; propertyName = undefined ; foundColon = false ; } } return new ObjectValue ( objectSpan , properties ) ; } function parseArray ( tokenizer , tokens , commentTokens ) { if ( ! tokenizer . current ) { throw new Error ( "Precondition failed" ) ; } let span = tokenizer . current . span ; const elements = [ ] ; next ( tokenizer , tokens , commentTokens ) ; let expectElement = true ; while ( tokenizer . current ) { span = span . union ( tokenizer . current . span ) ; if ( tokenizer . current . type === TokenType . RightSquareBracket ) { next ( tokenizer , tokens , commentTokens ) ; break ; } else if ( expectElement ) { const element = parseValue ( tokenizer , tokens , commentTokens ) ; if ( element ) { span = span . union ( element . span ) ; elements . push ( element ) ; expectElement = false ; } else { next ( tokenizer , tokens , commentTokens ) ; } } else { if ( tokenizer . current . type === TokenType . Comma ) { expectElement = true ; } next ( tokenizer , tokens , commentTokens ) ; } } return new ArrayValue ( span , elements ) ; } function next ( tokenizer , tokens , commentTokens ) { while ( tokenizer . moveNext ( ) ) { const current = tokenizer . current ! ; if ( current . type === TokenType . Comment ) { commentTokens . push ( current ) ; } else if ( current . type !== TokenType . Whitespace ) { tokens . push ( current ) ; break ; } } } /**
 * Traverses every node in a given Value tree
 */ export abstract class Visitor { public visitProperty ( property ) { if ( property ) { assert ( property . nameValue ) ; property . nameValue . accept ( this ) ; if ( property . value ) { property . value . accept ( this ) ; } } } public visitStringValue ( stringValue ) { } public visitNumberValue ( numberValue ) { } public visitBooleanValue ( booleanValue ) { } public visitObjectValue ( objectValue ) { if ( objectValue ) { for ( const property of objectValue . properties ) { property . accept ( this ) ; } } } public visitArrayValue ( arrayValue ) { if ( arrayValue ) { for ( const element of arrayValue . elements ) { assert ( element ) ; element . accept ( this ) ; } } } public visitNullValue ( nullValue ) { } } /**
 * A TLE visitor that searches a TLE value tree looking for references to the provided definition
 */ export class FindLineageVisitor extends Visitor { private _lineage : ( Json . ArrayValue | Json . ObjectValue | Json . Property ) [ ] ; private _foundLineageValues : ( Json . ArrayValue | Json . ObjectValue | Json . Property ) [ ] | undefined ; constructor ( private readonly searchDescendent ) { super ( ) ; this . _lineage = [ ] ; } public visitStringValue ( stringValue ) { if ( stringValue === this . searchDescendent ) { this . _found ( ) ; } } public visitNumberValue ( numberValue ) { if ( numberValue === this . searchDescendent ) { this . _found ( ) ; } } public visitBooleanValue ( booleanValue ) { if ( booleanValue === this . searchDescendent ) { this . _found ( ) ; } } public visitNullValue ( nullValue ) { if ( nullValue === this . searchDescendent ) { this . _found ( ) ; } } public visitProperty ( property ) { if ( ! this . _foundLineageValues && property ) { if ( property === this . searchDescendent ) { this . _found ( ) ; } else { this . _lineage . push ( property ) ; super . visitProperty ( property ) ; this . _lineage . pop ( ) ; } } } public visitObjectValue ( objectValue ) { if ( ! this . _foundLineageValues && objectValue ) { if ( objectValue === this . searchDescendent ) { this . _found ( ) ; } else { this . _lineage . push ( objectValue ) ; super . visitObjectValue ( objectValue ) ; this . _lineage . pop ( ) ; } } } public visitArrayValue ( arrayValue ) { if ( ! this . _foundLineageValues && arrayValue ) { if ( arrayValue === this . searchDescendent ) { this . _found ( ) ; } else { this . _lineage . push ( arrayValue ) ; super . visitArrayValue ( arrayValue ) ; this . _lineage . pop ( ) ; } } } private _found ( ) { this . _foundLineageValues = this . _lineage . slice ( ) ; } public static visit ( root , find ) { const visitor = new FindLineageVisitor ( find ) ; root . accept ( visitor ) ; return visitor . _foundLineageValues ; } }