private JsonPlusValue ResolveSubstitution(JsonPlusSubstitution sub) { JsonPlusObjectMember subField = sub.ParentMember; // first case, this substitution is a direct self-reference if (sub.Path == sub.GetMemberPath()) { IJsonPlusNode parent = sub.Parent; while (parent is JsonPlusValue) { parent = parent.Parent; } // Fail case if (parent is JsonPlusArray) { throw new JsonPlusException(RS.SelfRefSubstitutionInArray); } // try to resolve substitution by looking backward in the field assignment stack return(subField.OlderValueThan(sub)); } // need to recursively get full path JsonPlusPath fieldPath = subField.GetMemberPath(); // second case, the substitution references a field child in the past if (sub.Path.IsChildPathOf(fieldPath)) { JsonPlusValue olderValue = subField.OlderValueThan(sub); if ((olderValue != null) && (olderValue.Type == JsonPlusType.Object)) { int difLength = sub.Path.Count - fieldPath.Count; JsonPlusPath deltaPath = sub.Path.SubPath(sub.Path.Count - difLength, difLength); JsonPlusObject olderObject = olderValue.GetObject(); if (olderObject.TryGetValue(deltaPath, out JsonPlusValue innerValue)) { return(innerValue.Type == JsonPlusType.Object ? innerValue : null); } } } // Detect invalid parent-referencing substitution if (fieldPath.IsChildPathOf(sub.Path)) { throw new JsonPlusException(RS.SubstitutionRefDirectParentError); } // Detect invalid cyclic reference loop if (IsValueCyclic(subField, sub)) { throw new JsonPlusException(RS.CyclicSubstitutionLoop); } // third case, regular substitution _root.GetObject().TryGetValue(sub.Path, out JsonPlusValue field); return(field); }
/// <see cref="IJsonPlusNode.Clone(IJsonPlusNode)"/> public IJsonPlusNode Clone(IJsonPlusNode newParent) { JsonPlusObjectMember newField = new JsonPlusObjectMember(Path, (JsonPlusObject)newParent); newField._internalValues.AddRange(_internalValues); return(newField); }
public static bool IsSubstitution(this IJsonPlusNode value) { switch (value) { case JsonPlusValue v: foreach (IJsonPlusNode val in v) { if (val is JsonPlusSubstitution) { return(true); } } return(false); case JsonPlusObjectMember f: foreach (IJsonPlusNode v in f.Value) { if (v is JsonPlusSubstitution) { return(true); } } return(false); case JsonPlusSubstitution _: return(true); default: return(false); } }
/// <summary> /// Initializes a new instance of the <see cref="JsonPlusSubstitution"/> class. /// </summary> /// <param name="parent">The <see cref="JsonPlusValue"/> parent of this substitution.</param> /// <param name="path">The <see cref="JsonPlusPath"/> that this substitution is pointing to.</param> /// <param name="required">Indicates whether this is a lazy substitution. Lazy substitutions uses the `${?` notation.</param> /// <param name="location">The location of this substitution token in the source code, used for exception generation purposes.</param> internal JsonPlusSubstitution(IJsonPlusNode parent, JsonPlusPath path, ISourceLocation location, bool required) { Parent = parent ?? throw new ArgumentNullException(nameof(parent), RS.CannotSubstitutionRootNode); Column = location.Column; Line = location.Line; Required = required; Path = path; }
private static void Flatten(IJsonPlusNode node) { if (!(node is JsonPlusValue v)) { return; } switch (v.Type) { case JsonPlusType.Object: JsonPlusObject o = v.GetObject(); v.Clear(); v.Add(o); foreach (JsonPlusObjectMember item in o.Values) { Flatten(item); } break; case JsonPlusType.Array: List <IJsonPlusNode> a = v.GetArray(); v.Clear(); JsonPlusArray newArray = new JsonPlusArray(v); foreach (IJsonPlusNode item in a) { Flatten(item); newArray.Add(item); } v.Add(newArray); break; case JsonPlusType.Literal: if (v.Count == 1) { return; } string value = v.GetString(); v.Clear(); if (value == null) { v.Add(new NullValue(v)); } else if (value.NeedTripleQuotes()) { v.Add(new TripleQuotedStringValue(v, value)); } else if (value.NeedQuotes()) { v.Add(new QuotedStringValue(v, value)); } else { v.Add(new UnquotedStringValue(v, value)); } break; } }
/// <summary> /// Initializes a new instance of the <see cref="JsonPlusMergedObject"/> class. /// </summary> /// <param name="parent">The parent container.</param> /// <param name="objects">All <see cref="JsonPlusObject"/> items that are merged.</param> public JsonPlusMergedObject(IJsonPlusNode parent, List <JsonPlusObject> objects) : base(parent) { Objects = objects; foreach (JsonPlusObject obj in objects) { base.Merge(obj); } }
/// <see cref="JsonPlusValue.Equals(IJsonPlusNode)"/> public override bool Equals(IJsonPlusNode other) { if (other is null) { return(false); } return(other.Type == JsonPlusType.Empty); }
private IJsonPlusNode ParseSelfAssignArray(IJsonPlusNode owner) { // sanity check if (_tokens.Current.Type != TokenType.SelfAssignment) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInArray, TokenType.SelfAssignment, _tokens.Current.Type)); } // consume += operator token ConsumeWhitelines(); JsonPlusArray currentArray = new JsonPlusArray(owner); switch (_tokens.Current.Type) { case TokenType.Include: case TokenType.OptionalInclude: // #todo currentArray.Add(ParseInclude(currentArray)); break; case TokenType.StartOfArray: return(ParseArray(owner)); case TokenType.StartOfObject: // #todo currentArray.Add(ParseObject(currentArray)); break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { ConsumeWhitelines(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } currentArray.Add(ParseValue(currentArray)); return(currentArray); case TokenType.OptionalSubstitution: case TokenType.Substitution: JsonPlusPath pointerPath = JsonPlusPath.Parse(_tokens.Current.Value); JsonPlusSubstitution sub = new JsonPlusSubstitution(owner, pointerPath, _tokens.Current, _tokens.Current.Type == TokenType.Substitution); _substitutions.Add(sub); _tokens.Next(); return(sub); default: throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInArray, TokenType.EndOfArray, _tokens.Current.Type)); } return(currentArray); }
private object[] PopulateJPlusArray(List <IJsonPlusNode> jv, out ErrorRecord error) { if (jv == null) { throw new ArgumentNullException(nameof(jv)); } error = null; // do not recurse over the max if (_maxRecurseLevel > -1) { _recurseLevel += 1; if (_recurseLevel > _maxRecurseLevel) { return(null); } } List <object> results = new List <object>(); for (int i = 0; i < jv.Count; i++) { IJsonPlusNode current = jv[i]; if (current.Type == JsonPlusType.Empty) { results.Add(null); } else if (current.Type == JsonPlusType.Literal) { results.Add(PopulateJPlusLeaf(current.GetValue(), out error)); } else if (current.Type == JsonPlusType.Object) { results.Add(PopulateJPlusObject(current.GetObject(), out error)); _recurseLevel -= 1; } else if (current.Type == JsonPlusType.Array) { results.Add(PopulateJPlusArray(current.GetArray(), out error)); _recurseLevel -= 1; } else { error = new ErrorRecord(new JsonPlusException(string.Format(RS.JsonPlusAmbiguousTypeInArray, i)), "UnsupportedJsonPlusDataType", ErrorCategory.ParserError, null); } // terminate immediately if there's an error if (error != null) { return(null); } } return(results.ToArray()); }
/// <see cref="IJsonPlusNode.Clone(IJsonPlusNode)"/> public IJsonPlusNode Clone(IJsonPlusNode newParent) { JsonPlusObject clone = new JsonPlusObject(newParent); foreach (KeyValuePair <string, JsonPlusObjectMember> kvp in this) { clone[kvp.Key] = (JsonPlusObjectMember)kvp.Value.Clone(clone); } return(clone); }
/// <see cref="JsonPlusValue.Equals(IJsonPlusNode)"/> public bool Equals(IJsonPlusNode other) { if (other is null) { return(false); } if (ReferenceEquals(this, other)) { return(true); } return(Type == other.Type && string.Equals(Value, other.GetString())); }
/// <see cref="JsonPlusValue.Equals(IJsonPlusNode)"/> public bool Equals(IJsonPlusNode other) { if (other is null) { return(false); } if (ReferenceEquals(this, other)) { return(true); } return(other is JsonPlusObjectMember field && Path.Equals(field.Path) && Value.Equals(other)); }
internal JsonPlusValue OlderValueThan(IJsonPlusNode marker) { List <JsonPlusObject> objectList = new List <JsonPlusObject>(); int index = 0; while (index < _internalValues.Count) { JsonPlusValue value = _internalValues[index]; if (value.Any(v => ReferenceEquals(v, marker))) { break; } switch (value.Type) { case JsonPlusType.Object: objectList.Add(value.GetObject()); break; case JsonPlusType.Literal: case JsonPlusType.Array: objectList.Clear(); break; } index++; } if (objectList.Count == 0) { return(index == 0 ? null : _internalValues[index - 1]); } JsonPlusValue result = new JsonPlusValue(null); JsonPlusObject o = new JsonPlusObject(result); result.Add(o); foreach (JsonPlusObject obj in objectList) { o.Merge(obj); } return(result); }
/// <see cref="JsonPlusValue.Equals(IJsonPlusNode)"/> public bool Equals(IJsonPlusNode other) { if (other is null) { return(false); } if (ReferenceEquals(this, other)) { return(true); } if (other is JsonPlusSubstitution sub) { return((Path == sub.Path) && (GetMemberPath() == sub.GetMemberPath())); } return(!(_resolvedValue is null) && _resolvedValue.Equals(other)); }
/// <see cref="JsonPlusValue.Equals(IJsonPlusNode)"/> public bool Equals(IJsonPlusNode other) { if (other is null) { return(false); } if (ReferenceEquals(this, other)) { return(true); } if (other.Type != JsonPlusType.Object) { return(false); } return(this.AsEnumerable().SequenceEqual(other.GetObject().AsEnumerable())); }
internal static JsonPlusLiteralValue Create(IJsonPlusNode owner, Token token) { switch (token.LiteralType) { case LiteralTokenType.Null: return(new NullValue(owner)); case LiteralTokenType.Boolean: return(new BooleanValue(owner, token.Value)); case LiteralTokenType.Whitespace: return(new WhitespaceValue(owner, token.Value)); case LiteralTokenType.UnquotedLiteralValue: return(new UnquotedStringValue(owner, token.Value)); case LiteralTokenType.QuotedLiteralValue: return(new QuotedStringValue(owner, token.Value)); case LiteralTokenType.TripleQuotedLiteralValue: return(new TripleQuotedStringValue(owner, token.Value)); case LiteralTokenType.Integer: return(new IntegerValue(owner, token.Value)); case LiteralTokenType.Decimal: return(new DecimalValue(owner, token.Value)); case LiteralTokenType.Hexadecimal: return(new HexadecimalValue(owner, token.Value)); case LiteralTokenType.Octet: return(new OctetValue(owner, token.Value)); default: throw new JsonPlusException(string.Format(RS.UnknownLiteralToken, token.Value)); } }
/// <summary> /// Returns the <see cref="JsonPlusObjectMember"/> associated with the key specified. /// </summary> /// <param name="key">The key associated with the member to return.</param> /// <exception cref="ArgumentNullException">The <paramref name="key"/> specified is `null`.</exception> /// <exception cref="KeyNotFoundException">The <paramref name="key"/> does not exist in the <see cref="JsonPlusObject"/> instance.</exception> /// <returns>The <see cref="JsonPlusObjectMember"/> associated with <paramref name="key"/>.</returns> public JsonPlusObjectMember GetMember(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (!TryGetValue(key, out JsonPlusObjectMember item)) { IJsonPlusNode p = Parent; while (p != null && !(p is JsonPlusObjectMember)) { p = p.Parent; } string currentPath = (p != null) ? ((JsonPlusObjectMember)p).Path.Value + "." + key : key; string errorMessage = string.Format(RS.ObjectMemberNotFoundByPath, key, currentPath); throw new KeyNotFoundException(errorMessage); } return(item); }
/// <summary> /// Initializes a new instance of the <see cref="OctetValue"/> class. /// </summary> /// <param name="parent">The container object.</param> /// <param name="value">The underlying <see cref="string"/> of this value.</param> public OctetValue(IJsonPlusNode parent, string value) : base(parent, value) { }
// The owner in this context can be either an object or an array. private JsonPlusObject ParseObject(IJsonPlusNode owner) { JsonPlusObject jpObject = new JsonPlusObject(owner); if (_tokens.Current.Type != TokenType.StartOfObject && _tokens.Current.Type != TokenType.LiteralValue && _tokens.Current.Type != TokenType.Include) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject, TokenType.StartOfObject, TokenType.LiteralValue, _tokens.Current.Type)); } bool headless = true; if (_tokens.Current.Type == TokenType.StartOfObject) { headless = false; ConsumeWhitelines(); } IJsonPlusNode lastValue = null; bool parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: case TokenType.OptionalInclude: if (lastValue != null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject, TokenType.ArraySeparator, TokenType.EndOfLine, _tokens.Current.Type)); } lastValue = ParseInclude(jpObject); break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { ConsumeWhitespace(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (lastValue != null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject, TokenType.ArraySeparator, TokenType.EndOfLine, _tokens.Current.Type)); } lastValue = ParseObjectMember(jpObject); break; // TODO: can an object be declared floating without being assigned to a field? //case TokenType.StartOfObject: case TokenType.Comment: case TokenType.EndOfLine: switch (lastValue) { case null: ConsumeWhitelines(); break; case JsonPlusObjectMember _: break; default: ((JsonPlusValue)jpObject.Parent).Add(lastValue.GetObject()); break; } lastValue = null; ConsumeWhitelines(); break; case TokenType.ArraySeparator: switch (lastValue) { case null: throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject, TokenType.Assignment, TokenType.StartOfObject, _tokens.Current.Type)); case JsonPlusObjectMember _: break; default: ((JsonPlusValue)jpObject.Parent).Add(lastValue.GetObject()); break; } lastValue = null; ConsumeWhitelines(); break; case TokenType.EndOfObject: case TokenType.EndOfFile: if (headless && _tokens.Current.Type != TokenType.EndOfFile) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInObject, TokenType.EndOfFile, _tokens.Current.Type)); } if (!headless && _tokens.Current.Type != TokenType.EndOfObject) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInObject, TokenType.EndOfFile, _tokens.Current.Type)); } switch (lastValue) { case null: break; case JsonPlusObjectMember _: break; default: ((JsonPlusValue)jpObject.Parent).Add(lastValue.GetObject()); break; } lastValue = null; parsing = false; break; default: throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.ErrAtUnexpectedTokenInObject, _tokens.Current.Type)); } } if (_tokens.Current.Type == TokenType.EndOfObject) { _tokens.Next(); } return(jpObject); }
/// <summary> /// Initializes a new instance of the <see cref="TripleQuotedStringValue"/> class. /// </summary> /// <param name="parent">The container object.</param> /// <param name="value">The underlying <see cref="string"/> of this value.</param> public TripleQuotedStringValue(IJsonPlusNode parent, string value) : base(parent, value) { }
/// <see cref="IJsonPlusNode.Clone(IJsonPlusNode)"/> public override IJsonPlusNode Clone(IJsonPlusNode newParent) { return(new OctetValue(newParent, Value)); }
/// <see cref="IJsonPlusNode.Clone(IJsonPlusNode)"/> public override IJsonPlusNode Clone(IJsonPlusNode newParent) { return(new EmptyValue(newParent)); }
/// <see cref="IJsonPlusNode.Clone(IJsonPlusNode)"/> public override IJsonPlusNode Clone(IJsonPlusNode newParent) { return(new TripleQuotedStringValue(newParent, Value)); }
/// <see cref="JsonPlusValue.Add(IJsonPlusNode)"/> public override void Add(IJsonPlusNode value) { throw new JsonPlusException(string.Format(RS.ErrAddToNode, nameof(EmptyValue))); }
/// <summary> /// Initializes a new instance of the <see cref="WhitespaceValue"/> class. /// </summary> /// <param name="parent">The container object.</param> /// <param name="value">The underlying <see cref="string"/> of this value.</param> public WhitespaceValue(IJsonPlusNode parent, string value) : base(parent, value) { }
/// <summary> /// Initializes a new instance of the <see cref="EmptyValue"/> class. /// </summary> /// <param name="parent">The parent container.</param> public EmptyValue(IJsonPlusNode parent) : base(parent) { }
/// <see cref="IJsonPlusNode.Clone(IJsonPlusNode)"/> public override IJsonPlusNode Clone(IJsonPlusNode newParent) { return(new WhitespaceValue(newParent, Value)); }
private IJsonPlusNode ParseInclude(IJsonPlusNode owner) { // sanity check if (_tokens.Current.Type != TokenType.Include && _tokens.Current.Type != TokenType.OptionalInclude) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.InvalidTokenOnParseInclude, _tokens.Current.Type)); } bool required = _tokens.Current.Type == TokenType.Include; string fileName = null; Token includeToken = _tokens.Current; List <TokenType> expectedTokens = new List <TokenType>(new[] { TokenType.LiteralValue, TokenType.CloseBracket, TokenType.EndOfLine }); bool parsing = true; while (parsing) { if (!_tokens.GetNextSignificant(expectedTokens.ToArray())) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.InvalidTokenInInclude, _tokens.Current.Type), null); } switch (_tokens.Current.Type) { case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { ConsumeWhitespace(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (_tokens.Current.LiteralType != LiteralTokenType.QuotedLiteralValue) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.BadIncludeFileName, LiteralTokenType.QuotedLiteralValue, _tokens.Current.LiteralType)); } fileName = _tokens.Current.Value; expectedTokens.Remove(TokenType.LiteralValue); parsing = false; break; default: throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.ErrAtUnexpectedToken, _tokens.Current.Type)); } } if (fileName == null) { throw JsonPlusParserException.Create(_tokens.Current, Path, RS.FileNameMissingInInclude); } // Consume the last token _tokens.Next(); string includeSrc = _includeCallback(fileName).ConfigureAwait(false).GetAwaiter().GetResult(); if (string.IsNullOrWhiteSpace(includeSrc)) { if (required) { throw JsonPlusParserException.Create(includeToken, Path, RS.IncludeReturnEmptyError); } return(new EmptyValue(owner)); } JsonPlusRoot includeRoot = new JsonPlusParser().ParseSource(includeSrc, false, false, _includeCallback); if (owner != null && owner.Type != JsonPlusType.Empty && owner.Type != includeRoot.Value.Type) { throw JsonPlusParserException.Create(includeToken, Path, string.Format(RS.IncludeMergeTypeMismatch, owner.Type, includeRoot.Value.Type)); } // fixup the substitution, add the current path as a prefix to the substitution path foreach (JsonPlusSubstitution substitution in includeRoot.Substitutions) { substitution.Path.InsertRange(0, Path); } _substitutions.AddRange(includeRoot.Substitutions); // reparent the value returned by the callback to the owner of the include declaration return(includeRoot.Value.Clone(owner)); }
/// <summary> /// Retrieves the next array token from the tokenizer. /// </summary> /// <returns>An array of elements retrieved from the token.</returns> private JsonPlusArray ParseArray(IJsonPlusNode owner) { JsonPlusArray currentArray = new JsonPlusArray(owner); // consume start of array token ConsumeWhitelines(); IJsonPlusNode lastValue = null; bool parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: case TokenType.OptionalInclude: if (lastValue != null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInArray, TokenType.ArraySeparator, TokenType.EndOfLine, _tokens.Current.Type)); } lastValue = ParseInclude(currentArray); break; case TokenType.StartOfArray: if (lastValue != null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInArray, TokenType.ArraySeparator, TokenType.EndOfLine, _tokens.Current.Type)); } // Array inside of arrays are parsed as values because it can be value concatenated with another array. lastValue = ParseValue(currentArray); break; case TokenType.StartOfObject: if (lastValue != null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInArray, TokenType.ArraySeparator, TokenType.EndOfLine, _tokens.Current.Type)); } lastValue = ParseObject(currentArray); break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { ConsumeWhitespace(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (lastValue != null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInArray, TokenType.ArraySeparator, TokenType.EndOfLine, _tokens.Current.Type)); } lastValue = ParseValue(currentArray); break; case TokenType.OptionalSubstitution: case TokenType.Substitution: if (lastValue != null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInArray, TokenType.ArraySeparator, TokenType.EndOfLine, _tokens.Current.Type)); } lastValue = ParseValue(currentArray); break; case TokenType.Comment: case TokenType.EndOfLine: if (lastValue == null) { ConsumeWhitelines(); break; } currentArray.Add(lastValue); lastValue = null; ConsumeWhitelines(); break; case TokenType.ArraySeparator: if (lastValue == null) { throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.BadArrayType, _tokens.Current.Type)); } currentArray.Add(lastValue); lastValue = null; ConsumeWhitelines(); break; case TokenType.EndOfArray: if (lastValue != null) { currentArray.Add(lastValue); lastValue = null; } parsing = false; break; default: throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInArray, TokenType.EndOfArray, _tokens.Current.Type)); } } // Consume end of array token _tokens.Next(); return(currentArray); }
/// <summary> /// Retrieves the next value token from the tokenizer and appends it /// to the supplied element <paramref name="owner"/>. /// </summary> /// <param name="owner">The element to append the next token.</param> /// <exception cref="Exception">End of file reached while trying to read a value</exception> private JsonPlusValue ParseValue(IJsonPlusNode owner) { JsonPlusValue value = new JsonPlusValue(owner); bool parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: case TokenType.OptionalInclude: value.Add(ParseInclude(value)); break; case TokenType.LiteralValue: // Consume leading whitespaces. if (_tokens.Current.IsNonSignificant()) { ConsumeWhitespace(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } while (_tokens.Current.Type == TokenType.LiteralValue) { value.Add(JsonPlusLiteralValue.Create(value, _tokens.Current)); _tokens.Next(); } break; case TokenType.StartOfObject: value.Add(ParseObject(value)); break; case TokenType.StartOfArray: value.Add(ParseArray(value)); break; case TokenType.OptionalSubstitution: case TokenType.Substitution: JsonPlusPath pointerPath = JsonPlusPath.Parse(_tokens.Current.Value); JsonPlusSubstitution sub = new JsonPlusSubstitution(value, pointerPath, _tokens.Current, _tokens.Current.Type == TokenType.Substitution); _substitutions.Add(sub); value.Add(sub); _tokens.Next(); break; case TokenType.EndOfObject: case TokenType.EndOfArray: parsing = false; break; // comments automatically stop value parsing. case TokenType.Comment: ConsumeWhitelines(); parsing = false; break; case TokenType.EndOfLine: parsing = false; break; case TokenType.EndOfFile: case TokenType.ArraySeparator: parsing = false; break; case TokenType.SelfAssignment: JsonPlusSubstitution subAssign = new JsonPlusSubstitution(value, new JsonPlusPath(Path), _tokens.Current, false); _substitutions.Add(subAssign); value.Add(subAssign); value.Add(ParseSelfAssignArray(value)); parsing = false; break; case TokenType.Assignment: ConsumeWhitelines(); break; default: throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.ErrAtUnexpectedToken, _tokens.Current.Type)); } } // trim trailing whitespace if result is a literal if (value.Type == JsonPlusType.Literal) { if (value[value.Count - 1] is WhitespaceValue) { value.RemoveAt(value.Count - 1); } } return(value); }