private/runtime/Parser/JsonTokenizer.cs
using System; using System.Text; namespace Carbon.Json.Parser { using System.IO; using Internal.Extensions; public class JsonTokenizer : IDisposable { private readonly StringBuilder sb = new StringBuilder(); private readonly SourceReader reader; public JsonTokenizer(TextReader reader) : this(new SourceReader(reader)) { } internal JsonTokenizer(SourceReader reader) { this.reader = reader; reader.Next(); // Start with the first char } public JsonToken ReadNext() { reader.SkipWhitespace(); if (reader.IsEof) return JsonToken.Eof; switch (reader.Current) { case '"': return ReadQuotedString(); // Symbols case '[' : reader.Next(); return JsonToken.BracketOpen; // Array start case ']' : reader.Next(); return JsonToken.BracketClose; // Array end case ',' : reader.Next(); return JsonToken.Comma; // Value seperator case ':' : reader.Next(); return JsonToken.Colon; // Field value indicator case '{' : reader.Next(); return JsonToken.BraceOpen; // Object start case '}' : reader.Next(); return JsonToken.BraceClose; // Object end case '\0' : reader.Next(); return JsonToken.Terminator; // Stream terminiator default: return ReadLiteral(); } } private JsonToken ReadQuotedString() { Expect('"', "quoted string indicator"); reader.Next(); // Read '"' (Starting quote) // Read until we reach an unescaped quote char while (reader.Current != '"') { EnsureNotEof("quoted string"); if (reader.Current == '\\') { char escapedCharacter = reader.ReadEscapeCode(); sb.Append(escapedCharacter); continue; } StoreCurrentCharacterAndReadNext(); } reader.Next(); // Read '"' (Ending quote) return new JsonToken(TokenKind.String, value: sb.Extract()); } private JsonToken ReadLiteral() { if (char.IsDigit(reader.Current) || reader.Current == '-' || reader.Current == '+') { return ReadNumber(); } return ReadIdentifer(); } private JsonToken ReadNumber() { // Read until we hit a non-numeric character // -6.247737e-06 // E while (char.IsDigit(reader.Current) || reader.Current == '.' || reader.Current == 'e' || reader.Current == 'E' || reader.Current == '-' || reader.Current == '+') { StoreCurrentCharacterAndReadNext(); } return new JsonToken(TokenKind.Number, value: sb.Extract()); } int count = 0; private JsonToken ReadIdentifer() { count++; if (!char.IsLetter(reader.Current)) { throw new ParserException( message : $"Expected literal (number, boolean, or null). Was '{reader.Current}'.", location : reader.Location ); } // Read letters, numbers, and underscores '_' while (char.IsLetterOrDigit(reader.Current) || reader.Current == '_') { StoreCurrentCharacterAndReadNext(); } string text = sb.Extract(); switch (text) { case "true": return JsonToken.True; case "false": return JsonToken.False; case "null": return JsonToken.Null; default: return new JsonToken(TokenKind.String, text); } } private void Expect(char character, string description) { if (reader.Current != character) { throw new ParserException( message: $"Expected {description} ('{character}'). Was '{reader.Current}'.", location: reader.Location ); } } private void EnsureNotEof(string tokenType) { if (reader.IsEof) { throw new ParserException( message: $"Unexpected EOF while reading {tokenType}.", location: reader.Location ); } } private void StoreCurrentCharacterAndReadNext() { sb.Append(reader.Current); reader.Next(); } public void Dispose() { reader.Dispose(); } } } |