public ValueParseResult TryParse(ReadOnlySpan <byte> readerSpan, out string result, out int consumedLength, out int lineSpan, out int colSpan) { result = null; consumedLength = 0; lineSpan = 1; colSpan = 0; // is input empty if (readerSpan.Length <= 0) { // did any prior processing occur return(this._inString ? _cleanup(this, ValueParseResult.FailureEOF) : ValueParseResult.EOF); } // if we are not continuing, ensure it's a string that's being parsed var startPos = 0; if (!this._inString) { if (readerSpan[consumedLength++] != JsonTokens.QuoteMark) { if (Rune.DecodeFromUtf8(readerSpan, out var rune, out _) != OperationStatus.Done) { rune = default; } return(_cleanup(this, ValueParseResult.Failure("Unexpected token, expected \".", rune))); } startPos = consumedLength; this._inString = true; } // if continuing, check if anything is pending in the buffer var blen = (int)this._buffContent; Span <char> decoded = stackalloc char[512]; switch (this._buffContent) { // short escape: \" \\ \/ \b \f \n \r \t // long escape: \uXXXX case ContentType.EscapeSequence: case ContentType.ExtendedEscapeSequence: if (this._buffContent != ContentType.ExtendedEscapeSequence && readerSpan[0] == JsonTokens.UnicodePrefix) { this._buffContent = ContentType.ExtendedEscapeSequence; blen = (int)this._buffContent; } if (readerSpan.Length + this._buffPos < blen + 1) { readerSpan.CopyTo(this.Buffer[this._buffPos..].Span);
public ValueParseResult TryParse(ReadOnlySpan <byte> readerSpan, out bool result, out int consumedLength, out int lineSpan, out int colSpan) { result = false; consumedLength = 0; lineSpan = 1; colSpan = 0; // is input empty if (readerSpan.Length <= 0) { // did any prior processing occur return(this._buffPos > 0 ? _cleanup(this, ValueParseResult.FailureEOF) : ValueParseResult.EOF); } // determine what we're reading var expectedLength = 4; var src = this._buffPos > 0 ? this.Buffer.Span : readerSpan; switch (src[0]) { case JsonTokens.TrueFirst: result = true; break; case JsonTokens.FalseFirst: expectedLength = 5; break; default: this._buffPos = 0; if (Rune.DecodeFromUtf8(readerSpan, out var rune, out _) != OperationStatus.Done) { rune = default; } return(ValueParseResult.Failure("Unexpected token, expected true/false.", rune)); } // if reader buffer is too small, copy its contents then signal EOF var tooSmall = readerSpan.Length < expectedLength - this._buffPos; if (tooSmall || this._buffPos > 0) { var tlen = Math.Min(expectedLength - this._buffPos, readerSpan.Length); readerSpan.Slice(0, tlen).CopyTo(this.Buffer.Span[this._buffPos..]);
public ValueParseResult TryParse(ReadOnlySpan <byte> readerSpan, out double result, out int consumedLength, out int lineSpan, out int colSpan) { result = double.NaN; consumedLength = 0; lineSpan = 1; colSpan = 0; // if span is empty, and no parsing occured, signal EOF immediately if (readerSpan.Length <= 0 && this._lastPart == NumberPart.None) { return(ValueParseResult.EOF); } // if we are not continuing, check what we're parsing if (this.Buffer.Length == 0) { switch (readerSpan[consumedLength++]) { // a number in JSON can begin with - or digits 0-9 case JsonTokens.NumberSign: this._currentStructure = NumberStructure.HasSign; this._lastPart = NumberPart.NumberSign; break; // digit zero is a bit special in that if it's the first digit in a number, it becomes the only // legal digit before decimal point, hence special handling for it case JsonTokens.Digit0: this._currentStructure = NumberStructure.LeadingZero; this._lastPart = NumberPart.FirstDigit; break; // digits 1-9 are also valid as starting characters of a number, and unlike 0, they do not // restrict pre-decimal point digit count (though IEEE754 64-bit binary float limits still apply) case JsonTokens.Digit1: case JsonTokens.Digit2: case JsonTokens.Digit3: case JsonTokens.Digit4: case JsonTokens.Digit5: case JsonTokens.Digit6: case JsonTokens.Digit7: case JsonTokens.Digit8: case JsonTokens.Digit9: this._currentStructure = NumberStructure.LeadingNonzero; this._lastPart = NumberPart.FirstDigit; break; // not a legal character default: if (Rune.DecodeFromUtf8(readerSpan, out var rune, out _) != OperationStatus.Done) { rune = default; } return(ValueParseResult.Failure("Unexpected token, expected 0-9 or -.", rune)); } } // if we got empty when previous parsing occured, just don't parse, it's an end-of-content marker var completedParsing = false; if (readerSpan.Length > 0) { var offByOne = false; // try reading the number while (consumedLength < readerSpan.Length) { switch (readerSpan[consumedLength++]) { // digit 0 is special // if it's the first digit in the non-fractional part, it is the only legal digit before decimal point // otherwise it behaves like a regular digit // this means it can appear: // - as first digit before decimal point // - as non-first digit before decimal point, if first digit was not a 0 // - as a digit after decimal point before exponent mark // - as a digit after exponent mark or exponent sign // see: https://www.json.org/img/number.png case JsonTokens.Digit0: if (this._lastPart == NumberPart.FirstDigit && this._currentStructure.HasFlag(NumberStructure.LeadingZero)) { return(_cleanup(this, ValueParseResult.Failure("Digit in illegal separator. Expected decimal point.", new Rune(readerSpan[consumedLength - 1])))); } if (this._lastPart == NumberPart.NumberSign) { this._currentStructure |= NumberStructure.LeadingZero; this._lastPart = NumberPart.FirstDigit; } else { this._lastPart = this._lastPart switch { NumberPart.FirstDigit => NumberPart.Digit, NumberPart.FractionDot => NumberPart.FractionDigit, NumberPart.ExponentMarker or NumberPart.ExponentSign => NumberPart.ExponentDigit, _ => this._lastPart }; } break; // non-0 digits can appear: // - as first digit before decimal points // - as non-first digit before decimal point, if first digit was not a 0 // - as a digit after decimal point before exponent mark // - as a digit after exponent mark or exponent sign // see: https://www.json.org/img/number.png case JsonTokens.Digit1: case JsonTokens.Digit2: case JsonTokens.Digit3: case JsonTokens.Digit4: case JsonTokens.Digit5: case JsonTokens.Digit6: case JsonTokens.Digit7: case JsonTokens.Digit8: case JsonTokens.Digit9: if (this._lastPart == NumberPart.FirstDigit && this._currentStructure.HasFlag(NumberStructure.LeadingZero)) { return(_cleanup(this, ValueParseResult.Failure("Digit in illegal separator. Expected decimal point.", new Rune(readerSpan[consumedLength - 1])))); } if (this._lastPart == NumberPart.NumberSign) { this._currentStructure |= NumberStructure.LeadingNonzero; this._lastPart = NumberPart.FirstDigit; } else { this._lastPart = this._lastPart switch { NumberPart.FirstDigit => NumberPart.Digit, NumberPart.FractionDot => NumberPart.FractionDigit, NumberPart.ExponentMarker or NumberPart.ExponentSign => NumberPart.ExponentDigit, _ => this._lastPart }; } break; // decimal separator can appear only after at least one digit, and only once case JsonTokens.DecimalSeparator: if (this._lastPart != NumberPart.Digit && this._lastPart != NumberPart.FirstDigit) { return(_cleanup(this, ValueParseResult.Failure("Unexpected decimal separator.", new Rune('.')))); } this._currentStructure |= NumberStructure.Fraction; this._lastPart = NumberPart.FractionDot; break; // exponent marker can appear only after at least one digit, or at least one digit after // decimal point, and only once, regardless of variety case JsonTokens.ExponentSmall: case JsonTokens.ExponentCapital: if (this._lastPart != NumberPart.FirstDigit && this._lastPart != NumberPart.Digit && this._lastPart != NumberPart.FractionDigit) { return(_cleanup(this, ValueParseResult.Failure("Unexpected exponent marker.", new Rune(readerSpan[consumedLength - 1])))); } this._currentStructure |= NumberStructure.Exponent; this._lastPart = NumberPart.ExponentMarker; if (this._currentStructure.HasFlag(NumberStructure.Fraction)) { this._currentStructure |= NumberStructure.FractionValid; } break; // exponent sign can appear only after exponent marker case JsonTokens.NumberSign: case JsonTokens.ExponentSignPositive: if (this._lastPart != NumberPart.ExponentMarker) { return(_cleanup(this, ValueParseResult.Failure("Unexpected exponent sign.", new Rune(readerSpan[consumedLength - 1])))); } this._currentStructure |= NumberStructure.SignedExponent; this._lastPart = NumberPart.ExponentSign; break; // this is a situation where a non number-character is encountered // this is invalid if immediately after number sign, decimal point, exponent marker, or // exponent sign, otherwise consider it a completed number default: switch (this._lastPart) { case NumberPart.NumberSign: case NumberPart.FractionDot: case NumberPart.ExponentMarker: case NumberPart.ExponentSign: if (Rune.DecodeFromUtf8(readerSpan[(consumedLength - 1)..], out var rune, out _) != OperationStatus.Done) { rune = default; } return(_cleanup(this, ValueParseResult.Failure("Unexpected token, expected 0-9.", rune))); } offByOne = true; completedParsing = true; break; } // if parsing is completed, do not attempt to resume if (completedParsing) { break; } }
public ValueParseResult TryParse(ReadOnlySpan <byte> readerSpan, out ImmutableArray <JsonValue> result, out int consumedLength, out int lineSpan, out int colSpan) { result = default; consumedLength = 0; lineSpan = 1; colSpan = 0; // is input empty if (readerSpan.Length <= 0 && this._innerReader == null) { // did any prior processing occur return(this._arr != null ? _cleanup(this, ValueParseResult.FailureEOF) : ValueParseResult.EOF); } // if we are not continuing, ensure it's an object that's being parsed if (this._arr == null) { if (readerSpan[consumedLength++] != JsonTokens.OpeningBracket) { if (Rune.DecodeFromUtf8(readerSpan, out var rune, out _) != OperationStatus.Done) { rune = default; } return(_cleanup(this, ValueParseResult.Failure("Unexpected token, expected {.", rune))); } this._expectedNext = ExpectedToken.ValueOrEnd; this._arr = ImmutableArray.CreateBuilder <JsonValue>(); ++this._colSpan; ++this._streamPos; } // if continuing, check if any value is being parsed if (this._innerReader != null) { // valid only if expecting value if (this._expectedNext != ExpectedToken.Value && this._expectedNext != ExpectedToken.ValueOrEnd) { return(_cleanup(this, ValueParseResult.Failure("Invalid internal state.", default))); } // parse inner value ++consumedLength; var innerResult = this.ParseInner(readerSpan, ref consumedLength); switch (innerResult.Type) { case ValueParseResultType.Success: this._innerReader.Reset(); this._innerReader = null; break; case ValueParseResultType.EOF: return(innerResult); case ValueParseResultType.Failure: return(_cleanup(this, innerResult)); } } // read and parse array items var completedParsing = false; while (consumedLength < readerSpan.Length) { switch (readerSpan[consumedLength++]) { case JsonTokens.WhitespaceSpace: ++this._colSpan; ++this._streamPos; break; case JsonTokens.WhitespaceHorizontalTab: this._colSpan += 4; // fite me ++this._streamPos; break; case JsonTokens.WhitespaceCarriageReturn: // usually as part of CRLF, really no other reason for it to exist // old macs don't exist break; case JsonTokens.WhitespaceNewline: ++this._lineSpan; this._colSpan = 0; ++this._streamPos; break; case JsonTokens.ItemSeparator: if (this._expectedNext != ExpectedToken.ItemSeparatorOrEnd) { return(_cleanup(this, ValueParseResult.Failure("Unexpected item separator.", new Rune(JsonTokens.ItemSeparator)))); } ++this._colSpan; ++this._streamPos; this._expectedNext = ExpectedToken.Value; break; case JsonTokens.ClosingBracket: if (this._expectedNext != ExpectedToken.ItemSeparatorOrEnd && this._expectedNext != ExpectedToken.ValueOrEnd) { return(_cleanup(this, ValueParseResult.Failure("Unexpected array end.", new Rune(JsonTokens.ClosingBracket)))); } ++this._colSpan; ++this._streamPos; completedParsing = true; break; case JsonTokens.NullFirst: if (this._expectedNext != ExpectedToken.Value && this._expectedNext != ExpectedToken.ValueOrEnd) { return(_cleanup(this, ValueParseResult.Failure("Unexpected array item (null).", new Rune(JsonTokens.NullFirst)))); } this._innerReader = this._innerReaders.NullReader; break; case JsonTokens.TrueFirst: case JsonTokens.FalseFirst: if (this._expectedNext != ExpectedToken.Value && this._expectedNext != ExpectedToken.ValueOrEnd) { return(_cleanup(this, ValueParseResult.Failure("Unexpected array item (boolean).", new Rune(readerSpan[consumedLength - 1])))); } this._innerReader = this._innerReaders.BooleanReader; break; case JsonTokens.NumberSign: case JsonTokens.Digit0: case JsonTokens.Digit1: case JsonTokens.Digit2: case JsonTokens.Digit3: case JsonTokens.Digit4: case JsonTokens.Digit5: case JsonTokens.Digit6: case JsonTokens.Digit7: case JsonTokens.Digit8: case JsonTokens.Digit9: if (this._expectedNext != ExpectedToken.Value && this._expectedNext != ExpectedToken.ValueOrEnd) { return(_cleanup(this, ValueParseResult.Failure("Unexpected array item (number).", new Rune(readerSpan[consumedLength - 1])))); } this._innerReader = this._innerReaders.NumberReader; break; case JsonTokens.QuoteMark: if (this._expectedNext != ExpectedToken.Value && this._expectedNext != ExpectedToken.ValueOrEnd) { return(_cleanup(this, ValueParseResult.Failure("Unexpected array item (string).", new Rune(JsonTokens.QuoteMark)))); } this._innerReader = this._innerReaders.StringReader; break; case JsonTokens.OpeningBracket: if (this._expectedNext != ExpectedToken.Value && this._expectedNext != ExpectedToken.ValueOrEnd) { return(_cleanup(this, ValueParseResult.Failure("Unexpected array item (array).", new Rune(JsonTokens.OpeningBracket)))); } this._innerReader = new JsonArrayReader(this._innerReaders); break; case JsonTokens.OpeningBrace: if (this._expectedNext != ExpectedToken.Value && this._expectedNext != ExpectedToken.ValueOrEnd) { return(_cleanup(this, ValueParseResult.Failure("Unexpected array item (object).", new Rune(JsonTokens.OpeningBracket)))); } this._innerReader = new JsonObjectReader(this._innerReaders); break; default: if (Rune.DecodeFromUtf8(readerSpan[(consumedLength - 1)..], out var rune, out _) != OperationStatus.Done)