Skip to content

Commit

Permalink
Initial JSON parser and renderer. Not very pretty 😅
Browse files Browse the repository at this point in the history
  • Loading branch information
patriksvensson committed Nov 22, 2022
1 parent c1e9c1a commit bb820c2
Show file tree
Hide file tree
Showing 18 changed files with 604 additions and 2 deletions.
15 changes: 15 additions & 0 deletions examples/Console/Json/Json.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ExampleTitle>Json</ExampleTitle>
<ExampleDescription>Displays the capabilities of the current console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Shared\Shared.csproj" />
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions examples/Console/Json/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Spectre.Console;

namespace Info;

public static class Program
{
public static void Main()
{
var json = new JsonText("{ \"hello\": 32, \"World\": { \"foo\": 21, \"bar\": 255 } }");
AnsiConsole.Write(
new Panel(new Padder(json, new Padding(2, 1)))
{
Width = 35,
}
.Header("Some JSON in a panel")
.Collapse()
.RoundedBorder()
.BorderColor(Color.Yellow));
}
}
14 changes: 14 additions & 0 deletions examples/Examples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "..\s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layout", "Console\Layout\Layout.csproj", "{A9FDE73A-8452-4CA3-B366-3F900597E132}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Json", "Console\Json\Json.csproj", "{ABE3E734-0756-4D5A-B28A-E6E526D9927D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -521,6 +523,18 @@ Global
{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x64.Build.0 = Release|Any CPU
{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.ActiveCfg = Release|Any CPU
{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.Build.0 = Release|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|x64.ActiveCfg = Debug|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|x64.Build.0 = Debug|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|x86.ActiveCfg = Debug|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|x86.Build.0 = Debug|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|Any CPU.Build.0 = Release|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x64.ActiveCfg = Release|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x64.Build.0 = Release|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x86.ActiveCfg = Release|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
10 changes: 10 additions & 0 deletions src/Spectre.Console/Extensions/CharExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,14 @@ public static int GetCellWidth(this char character)
{
return Cell.GetCellLength(character);
}

internal static bool IsDigit(this char character)
{
return character >= '0' && character <= '9';
}

internal static bool IsDigitLargerThanZero(this char character)
{
return character >= '1' && character <= '9';
}
}
15 changes: 13 additions & 2 deletions src/Spectre.Console/Internal/Text/StringBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,22 @@ public void Dispose()
_reader.Dispose();
}

public char Expect(char character)
{
var read = Read();
if (read != character)
{
throw new InvalidOperationException($"Expected '{character}', but found '{read}'");
}

return read;
}

public char Peek()
{
if (Eof)
{
throw new InvalidOperationException("Tried to peek past the end of the text.");
return '\0';
}

return (char)_reader.Peek();
Expand All @@ -37,7 +48,7 @@ public char Read()
{
if (Eof)
{
throw new InvalidOperationException("Tried to read past the end of the text.");
return '\0';
}

Position++;
Expand Down
72 changes: 72 additions & 0 deletions src/Spectre.Console/Widgets/Json/JsonParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace Spectre.Console;

internal static class JsonParser
{
public static JsonSyntax Parse(List<JsonToken> tokens)
{
var reader = new JsonTokenReader(tokens);
return Parse(reader);
}

private static JsonSyntax Parse(JsonTokenReader reader)
{
var current = reader.Peek();
if (current?.Type != JsonTokenType.LeftBrace)
{
throw new InvalidOperationException("Invalid JSON");
}

return ParseObject(reader);
}

private static JsonSyntax ParseObject(JsonTokenReader reader)
{
reader.Consume(JsonTokenType.LeftBrace);

var result = new JsonObject();

if (reader.Peek()?.Type != JsonTokenType.RightBrace)
{
while (true)
{
var name = reader.Consume(JsonTokenType.String);
reader.Consume(JsonTokenType.Colon);
var value = ParseValue(reader);

result.Properties.Add(new JsonProperty(name.Lexeme, value));

if (reader.Peek()?.Type != JsonTokenType.Comma)
{
break;
}

reader.Consume(JsonTokenType.Comma);
}
}

reader.Consume(JsonTokenType.RightBrace);
return result;
}

private static JsonSyntax ParseValue(JsonTokenReader reader)
{
var current = reader.Peek();
if (current == null)
{
throw new InvalidOperationException("Could not parse value");
}

if (current.Type == JsonTokenType.LeftBrace)
{
return ParseObject(reader);
}

if (current.Type == JsonTokenType.Number)
{
reader.Consume(JsonTokenType.Number);
return new JsonNumber(current.Lexeme);
}

throw new InvalidOperationException("Unknown value token");
}
}
94 changes: 94 additions & 0 deletions src/Spectre.Console/Widgets/Json/JsonText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
namespace Spectre.Console;

public sealed class JsonText : JustInTimeRenderable
{
private readonly JsonSyntax _syntax;

public JsonText(string json)
{
var tokens = JsonTokenizer.Tokenize(json);
_syntax = JsonParser.Parse(tokens);
}

protected override IRenderable Build()
{
var paragraph = new Paragraph();
_syntax.Accept(JsonParagraphCreator.Shared, new JsonContext(paragraph, new JsonTextStyles()));
return paragraph;
}

private sealed class JsonParagraphCreator : JsonSyntaxVisitor<JsonContext>
{
public static JsonParagraphCreator Shared { get; } = new JsonParagraphCreator();

public override void VisitNumber(JsonNumber syntax, JsonContext context)
{
context.Paragraph.Append(syntax.Lexeme, new Style(Color.Red));
}

public override void VisitObject(JsonObject syntax, JsonContext context)
{
context.Paragraph.Append("{");
context.Paragraph.Append("\n");
context.IncreaseIndentation();

foreach (var (_, _, last, property) in syntax.Properties.Enumerate())
{
context.InsertIndentation();
property.Accept(this, context);

if (!last)
{
context.Paragraph.Append(",", new Style(Color.Green));
}

context.Paragraph.Append("\n");
}

context.DecreaseIndentation();
context.InsertIndentation();
context.Paragraph.Append("}");
}

public override void VisitProperty(JsonProperty syntax, JsonContext context)
{
context.Paragraph.Append(syntax.Name, new Style(Color.Blue));
context.Paragraph.Append(":", new Style(Color.Yellow));
context.Paragraph.Append(" ");

syntax.Value.Accept(this, context);
}
}
}

internal sealed class JsonContext
{
public Paragraph Paragraph { get; }
public int Indentation { get; private set; }
public JsonTextStyles Styling { get; }

public JsonContext(Paragraph paragraph, JsonTextStyles styling)
{
Paragraph = paragraph;
Styling = styling;
}

public void IncreaseIndentation()
{
Indentation++;
}

public void DecreaseIndentation()
{
Indentation = Math.Max(0, Indentation - 1);
}

public void InsertIndentation()
{
Paragraph.Append(new string(' ', Indentation * 3));
}
}

internal sealed class JsonTextStyles
{
}
13 changes: 13 additions & 0 deletions src/Spectre.Console/Widgets/Json/JsonToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Spectre.Console;

internal sealed class JsonToken
{
public JsonTokenType Type { get; }
public string Lexeme { get; }

public JsonToken(JsonTokenType type, string lexeme)
{
Type = type;
Lexeme = lexeme ?? throw new ArgumentNullException(nameof(lexeme));
}
}
50 changes: 50 additions & 0 deletions src/Spectre.Console/Widgets/Json/JsonTokenReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace Spectre.Console;

internal sealed class JsonTokenReader
{
private readonly List<JsonToken> _reader;
private readonly int _length;

public int Position { get; private set; }
public bool Eof => Position >= _length;

public JsonTokenReader(List<JsonToken> tokens)
{
_reader = tokens;
_length = tokens.Count;

Position = 0;
}

public JsonToken Consume(JsonTokenType type)
{
var read = Read();
if (read?.Type != type)
{
throw new InvalidOperationException($"Expected '{type}' token");
}

return read;
}

public JsonToken? Peek()
{
if (Eof)
{
return null;
}

return _reader[Position];
}

public JsonToken? Read()
{
if (Eof)
{
return null;
}

Position++;
return _reader[Position - 1];
}
}
15 changes: 15 additions & 0 deletions src/Spectre.Console/Widgets/Json/JsonTokenType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Spectre.Console;

internal enum JsonTokenType
{
LeftBrace,
RightBrace,
LeftBracket,
RightBracket,
Colon,
Comma,
String,
Number,
Boolean,
Null,
}
Loading

0 comments on commit bb820c2

Please sign in to comment.