Skip to content

Commit b22ca82

Browse files
committed
Added JSON implementation.
1 parent b560b76 commit b22ca82

File tree

9 files changed

+550
-0
lines changed

9 files changed

+550
-0
lines changed

Nitra.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowGenerator", "Parser
3535
EndProject
3636
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WiWorkflowTests", "Tests\WiWorkflowTests\WiWorkflowTests.csproj", "{01C42F92-EB73-4BD1-8706-80C47D4ECA9C}"
3737
EndProject
38+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Json", "Json", "{1AB39F20-22B4-4C54-99E2-65960EDCD4AA}"
39+
EndProject
40+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Json", "Parsers\Json\Json.csproj", "{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B}"
41+
EndProject
3842
Global
3943
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4044
Debug|Any CPU = Debug|Any CPU
@@ -123,6 +127,14 @@ Global
123127
{01C42F92-EB73-4BD1-8706-80C47D4ECA9C}.Release|Any CPU.Build.0 = Release|Any CPU
124128
{01C42F92-EB73-4BD1-8706-80C47D4ECA9C}.Release|x64.ActiveCfg = Release|Any CPU
125129
{01C42F92-EB73-4BD1-8706-80C47D4ECA9C}.Release|x64.Build.0 = Release|Any CPU
130+
{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
131+
{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B}.Debug|Any CPU.Build.0 = Debug|Any CPU
132+
{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B}.Debug|x64.ActiveCfg = Debug|Any CPU
133+
{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B}.Debug|x64.Build.0 = Debug|Any CPU
134+
{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B}.Release|Any CPU.ActiveCfg = Release|Any CPU
135+
{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B}.Release|Any CPU.Build.0 = Release|Any CPU
136+
{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B}.Release|x64.ActiveCfg = Release|Any CPU
137+
{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B}.Release|x64.Build.0 = Release|Any CPU
126138
EndGlobalSection
127139
GlobalSection(SolutionProperties) = preSolution
128140
HideSolutionNode = FALSE
@@ -136,6 +148,8 @@ Global
136148
{36CF96E6-1EC5-4E4D-A3F7-7C2B5FA22F56} = {4318483A-5409-4282-8D0B-62D10DB6A8D4}
137149
{B372BCFB-B1DF-4FBF-8C65-2942920B5C57} = {4318483A-5409-4282-8D0B-62D10DB6A8D4}
138150
{01C42F92-EB73-4BD1-8706-80C47D4ECA9C} = {BFC5E780-F61A-4D8B-84BF-939EB831FD84}
151+
{1AB39F20-22B4-4C54-99E2-65960EDCD4AA} = {CAB1ED79-7BF8-4523-A747-DB4DB117D1D3}
152+
{F8A7A9B3-BFD2-47EB-826D-C3538BB2F44B} = {1AB39F20-22B4-4C54-99E2-65960EDCD4AA}
139153
EndGlobalSection
140154
GlobalSection(ExtensibilityGlobals) = postSolution
141155
SolutionGuid = {1CFBB9ED-FF57-4990-B1C7-F31EB1F3BDAB}

Parsers/Dot/DotParser/DotParser.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@
1818
<ProjectReference Include="..\..\..\TerminalGenerator\TerminalGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
1919
</ItemGroup>
2020

21+
<ItemGroup>
22+
<Folder Include="Shared\" />
23+
</ItemGroup>
24+
2125
</Project>

Parsers/Json/Ast.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace Json;
2+
3+
public abstract record JsonAst(int StartPos, int EndPos)
4+
{
5+
public abstract override string ToString();
6+
}
7+
8+
public record JsonObject(IReadOnlyList<JsonProperty> Properties, int StartPos, int EndPos) : JsonAst(StartPos, EndPos)
9+
{
10+
public override string ToString() => $"{{{string.Join(", ", Properties)}}}";
11+
}
12+
13+
public record JsonArray(IReadOnlyList<JsonAst> Elements, int StartPos, int EndPos) : JsonAst(StartPos, EndPos)
14+
{
15+
public override string ToString() => $"[{string.Join(", ", Elements)}]";
16+
}
17+
18+
public record JsonString(string Value, int StartPos, int EndPos) : JsonAst(StartPos, EndPos)
19+
{
20+
public override string ToString() => $"\"{Value}\"";
21+
}
22+
23+
public record JsonNumber(double Value, int StartPos, int EndPos) : JsonAst(StartPos, EndPos)
24+
{
25+
public override string ToString() => Value.ToString();
26+
}
27+
28+
public record JsonBoolean(bool Value, int StartPos, int EndPos) : JsonAst(StartPos, EndPos)
29+
{
30+
public override string ToString() => Value ? "true" : "false";
31+
}
32+
33+
public record JsonNull(int StartPos, int EndPos) : JsonAst(StartPos, EndPos)
34+
{
35+
public override string ToString() => "null";
36+
}
37+
38+
public record JsonProperty(string Name, JsonAst Value, int StartPos, int EndPos) : JsonAst(StartPos, EndPos)
39+
{
40+
public override string ToString() => $"\"{Name}\": {Value}";
41+
}

Parsers/Json/Json.csproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<LangVersion>preview</LangVersion>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Compile Include="..\..\Shared\NetStandard2_0Support.cs" Link="Shared\NetStandard2_0Support.cs" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\ExtensibleParaser\ExtensibleParaser.csproj" />
16+
<ProjectReference Include="..\..\Regex\Regex\Regex.csproj" OutputItemType="Analyzer" />
17+
<ProjectReference Include="..\..\TerminalGenerator\TerminalGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<Folder Include="Shared\" />
22+
</ItemGroup>
23+
24+
</Project>

Parsers/Json/JsonParser.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using ExtensibleParaser;
2+
3+
namespace Json;
4+
5+
public class JsonParser
6+
{
7+
private readonly Parser _parser;
8+
9+
public JsonParser()
10+
{
11+
_parser = new Parser(JsonTerminals.Whitespace());
12+
13+
_parser.Rules["Json"] = [new Ref("Value")];
14+
15+
_parser.Rules["Value"] = [
16+
new Seq([
17+
new Literal("{"),
18+
new SeparatedList(
19+
new Seq([
20+
JsonTerminals.String(),
21+
new Literal(":"),
22+
new Ref("Value")
23+
], "Property"),
24+
new Literal(","),
25+
"Properties",
26+
SeparatorEndBehavior.Forbidden,
27+
CanBeEmpty: true
28+
),
29+
new Literal("}")
30+
], "Object"),
31+
new Seq([
32+
new Literal("["),
33+
new SeparatedList(
34+
new Ref("Value"),
35+
new Literal(","),
36+
"Elements",
37+
SeparatorEndBehavior.Forbidden,
38+
CanBeEmpty: true
39+
),
40+
new Literal("]")
41+
], "Array"),
42+
JsonTerminals.String(),
43+
JsonTerminals.Number(),
44+
JsonTerminals.True(),
45+
JsonTerminals.False(),
46+
JsonTerminals.Null()
47+
];
48+
49+
_parser.BuildTdoppRules("Json");
50+
}
51+
52+
public JsonAst Parse(string input)
53+
{
54+
var result = _parser.Parse(input, "Json", out _);
55+
if (!result.TryGetSuccess(out var node, out _))
56+
throw new InvalidOperationException($"Parse failed: {_parser.ErrorInfo.GetErrorText()}");
57+
58+
var visitor = new JsonVisitor(input);
59+
node.Accept(visitor);
60+
return visitor.Result ?? throw new InvalidOperationException("Parsing resulted in null AST");
61+
}
62+
}

Parsers/Json/JsonTerminals.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using ExtensibleParaser;
2+
3+
namespace Json;
4+
5+
[TerminalMatcher]
6+
public sealed partial class JsonTerminals
7+
{
8+
[Regex(@"\s*")]
9+
public static partial Terminal Whitespace();
10+
11+
[Regex("""
12+
("([^"\\]|\\.)*"|'([^'\\]|\\.)*')
13+
""")]
14+
public static partial Terminal String();
15+
16+
[Regex(@"-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?")]
17+
public static partial Terminal Number();
18+
19+
[Regex("true")]
20+
public static partial Terminal True();
21+
22+
[Regex("false")]
23+
public static partial Terminal False();
24+
25+
[Regex("null")]
26+
public static partial Terminal Null();
27+
}

Parsers/Json/JsonVisitor.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using System.Text;
2+
3+
using ExtensibleParaser;
4+
5+
namespace Json;
6+
7+
public class JsonVisitor(string input) : ISyntaxVisitor
8+
{
9+
public JsonAst? Result { get; private set; }
10+
11+
public void Visit(TerminalNode node)
12+
{
13+
var span = node.AsSpan(input).ToString();
14+
Result = node.Kind switch
15+
{
16+
"String" => new JsonString(ProcessJsonString(span[1..^1]), node.StartPos, node.EndPos),
17+
"Number" => new JsonNumber(double.Parse(span), node.StartPos, node.EndPos),
18+
"True" => new JsonBoolean(true, node.StartPos, node.EndPos),
19+
"False" => new JsonBoolean(false, node.StartPos, node.EndPos),
20+
"Null" => new JsonNull(node.StartPos, node.EndPos),
21+
"{" or "}" or "[" or "]" or ":" or "," => null, // Игнорируем литералы-символы
22+
_ => throw new InvalidOperationException($"Unknown terminal: {node.Kind}")
23+
};
24+
}
25+
26+
public void Visit(SeqNode node)
27+
{
28+
var children = new List<JsonAst>();
29+
foreach (var element in node.Elements)
30+
{
31+
element.Accept(this);
32+
if (Result != null)
33+
children.Add(Result);
34+
}
35+
36+
Result = node.Kind switch
37+
{
38+
"Object" => HandleObjectNode(children, node),
39+
"Property" => HandlePropertyNode(children, node),
40+
"Array" => HandleArrayNode(children, node),
41+
_ => throw new InvalidOperationException($"Unknown sequence: {node.Kind}")
42+
};
43+
}
44+
45+
public void Visit(ListNode node)
46+
{
47+
var elements = new List<JsonAst>();
48+
foreach (var element in node.Elements)
49+
{
50+
element.Accept(this);
51+
if (Result != null)
52+
elements.Add(Result);
53+
}
54+
55+
Result = node.Kind switch
56+
{
57+
"Properties" => new JsonObject(elements.OfType<JsonProperty>().ToList(), node.StartPos, node.EndPos),
58+
"Elements" => new JsonArray(elements, node.StartPos, node.EndPos),
59+
_ => throw new InvalidOperationException($"Unknown list: {node.Kind}")
60+
};
61+
}
62+
63+
public void Visit(SomeNode node) => node.Value.Accept(this);
64+
65+
public void Visit(NoneNode _) => Result = null;
66+
67+
private JsonObject HandleObjectNode(List<JsonAst> children, SeqNode node)
68+
{
69+
if (children.Count == 1 && children[0] is JsonObject propertiesObject)
70+
return new JsonObject(propertiesObject.Properties, node.StartPos, node.EndPos);
71+
else
72+
return new JsonObject(children.OfType<JsonProperty>().ToArray(), node.StartPos, node.EndPos);
73+
}
74+
75+
private JsonProperty HandlePropertyNode(List<JsonAst> children, SeqNode node)
76+
{
77+
if (children is [JsonString key, JsonAst value])
78+
return new JsonProperty(key.Value, value, node.StartPos, node.EndPos);
79+
80+
throw new InvalidOperationException($"Invalid property structure. Expected [String, Value], got [{string.Join(", ", children.Select(c => c.GetType().Name))}]");
81+
}
82+
83+
private JsonArray HandleArrayNode(List<JsonAst> children, SeqNode node)
84+
{
85+
if (children.Count == 1 && children[0] is JsonArray elementsArray)
86+
return new JsonArray(elementsArray.Elements, node.StartPos, node.EndPos);
87+
else
88+
return new JsonArray(children.Where(c => c is not JsonObject).ToArray(), node.StartPos, node.EndPos);
89+
}
90+
91+
private static string ProcessJsonString(string input)
92+
{
93+
var result = new StringBuilder();
94+
for (int i = 0; i < input.Length; i++)
95+
{
96+
if (input[i] == '\\' && i + 1 < input.Length)
97+
{
98+
switch (input[i + 1])
99+
{
100+
case '"': result.Append('"'); i++; break;
101+
case '\\': result.Append('\\'); i++; break;
102+
case '/': result.Append('/'); i++; break;
103+
case 'b': result.Append('\b'); i++; break;
104+
case 'f': result.Append('\f'); i++; break;
105+
case 'n': result.Append('\n'); i++; break;
106+
case 'r': result.Append('\r'); i++; break;
107+
case 't': result.Append('\t'); i++; break;
108+
case 'u' when i + 5 < input.Length:
109+
var hex = input.Substring(i + 2, 4);
110+
result.Append((char)Convert.ToInt32(hex, 16));
111+
i += 5;
112+
break;
113+
default: result.Append(input[i + 1]); i++; break;
114+
}
115+
}
116+
else
117+
result.Append(input[i]);
118+
}
119+
120+
return result.ToString();
121+
}
122+
}

0 commit comments

Comments
 (0)