feat: 将LR分析表生成到代码中 (#27)

Reviewed-on: PostGuard/Canon#27
This commit is contained in:
2024-04-08 19:46:24 +08:00
parent 5e3ea6303e
commit 1690187c0a
14 changed files with 1125 additions and 22 deletions

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Canon.Core\Canon.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,40 @@
using System.CommandLine;
using Canon.Generator.GrammarGenerator;
namespace Canon.Generator.Extensions;
public static class RootCommandExtension
{
public static void AddGenerateCommand(this RootCommand rootCommand)
{
Command generateCommand = new("generate", "Generate source files.");
Argument<string> filenameArgument = new(name: "filename",
description: "determines the generated file name.",
getDefaultValue: () => "Canon.g.cs");
generateCommand.AddArgument(filenameArgument);
Option<string> namespaceOption = new(name: "--namespace",
description: "determines the namespace of generated code.",
getDefaultValue: () => "Canon.Generator.GrammarGenerator");
generateCommand.AddOption(namespaceOption);
generateCommand.SetHandler(async (context) =>
{
string filename = context.ParseResult.GetValueForArgument(filenameArgument);
FileInfo generatedFile = new(Path.Combine(Environment.CurrentDirectory, filename));
if (generatedFile.Exists)
{
generatedFile.Delete();
}
await using FileStream stream = generatedFile.OpenWrite();
GenerateCommand command = new();
string namespaceValue = context.ParseResult.GetValueForOption(namespaceOption) ?? "Canon.Generator.GrammarGenerator";
await command.GenerateCode(stream, namespaceValue);
});
rootCommand.AddCommand(generateCommand);
}
}

View File

@@ -0,0 +1,29 @@
using System.Text;
using Canon.Core.Abstractions;
using Canon.Core.Enums;
using Canon.Core.GrammarParser;
namespace Canon.Generator.GrammarGenerator;
public class GenerateCommand
{
private readonly GrammarBuilder _builder = new()
{
Generators = PascalGrammar.Grammar, Begin = new NonTerminator(NonTerminatorType.StartNonTerminator)
};
private readonly GeneratedGrammarParser _parser;
public GenerateCommand()
{
Grammar grammar = _builder.Build();
_parser = grammar.ToGeneratedGrammarParser();
}
public async Task GenerateCode(Stream output, string namespaceValue)
{
string code = _parser.GenerateCode(namespaceValue);
byte[] bytes = Encoding.UTF8.GetBytes(code);
await output.WriteAsync(bytes);
}
}

View File

@@ -0,0 +1,116 @@
using System.Text;
using Canon.Core.Abstractions;
using Canon.Core.GrammarParser;
namespace Canon.Generator.GrammarGenerator;
public class GeneratedGrammarParser(
Dictionary<string, GeneratedTransformer> transformers,
string beginState,
NonTerminator begin) : GrammarParserBase
{
public override ITransformer BeginTransformer => transformers[beginState];
public override NonTerminator Begin => begin;
public string GenerateCode(string namespaceValue)
{
StringBuilder builder = new();
builder.Append("#nullable enable\n");
builder.Append("using Canon.Core.Abstractions;\n");
builder.Append("using Canon.Core.GrammarParser;\n");
builder.Append("using Canon.Core.Enums;\n");
builder.Append($"namespace {namespaceValue};\n");
builder.Append('\n');
builder.Append("""
public class GeneratedTransformer : ITransformer
{
private IDictionary<TerminatorBase, string> _shiftPointers;
public string Name { get; }
public IDictionary<Terminator, ReduceInformation> ReduceTable { get; }
public IDictionary<TerminatorBase, ITransformer> ShiftTable { get; }
public GeneratedTransformer(Dictionary<TerminatorBase, string> shiftTable,
Dictionary<Terminator, ReduceInformation> reduceTable, string name)
{
ReduceTable = reduceTable;
ShiftTable = new Dictionary<TerminatorBase, ITransformer>();
_shiftPointers = shiftTable;
Name = name;
}
public GeneratedTransformer()
{
ReduceTable = new Dictionary<Terminator, ReduceInformation>();
ShiftTable = new Dictionary<TerminatorBase, ITransformer>();
_shiftPointers = new Dictionary<TerminatorBase, string>();
Name = Guid.NewGuid().ToString();
}
public void ConstructShiftTable(Dictionary<string, GeneratedTransformer> transformers)
{
foreach (KeyValuePair<TerminatorBase,string> pair in _shiftPointers)
{
ShiftTable.Add(pair.Key, transformers[pair.Value]);
}
}
public override bool Equals(object? obj)
{
if (obj is not GeneratedTransformer other)
{
return false;
}
return Name == other.Name;
}
public override int GetHashCode() => Name.GetHashCode();
}
""");
builder.Append('\n');
builder.Append("public class GeneratedGrammarParser : GrammarParserBase\n")
.Append("{\n");
builder.Append("\tprivate static readonly Dictionary<string, GeneratedTransformer> s_transformers = new()\n")
.Append("\t{\n");
foreach (KeyValuePair<string, GeneratedTransformer> pair in transformers)
{
builder.Append($"\t\t{{ \"{pair.Key}\", {pair.Value.GenerateCode()} }},\n");
}
builder.Append("\t};\n");
builder.Append("\n");
builder.Append("""
private GeneratedGrammarParser()
{
foreach(GeneratedTransformer transformer in s_transformers.Values)
{
transformer.ConstructShiftTable(s_transformers);
}
}
private static GeneratedGrammarParser s_instance = new GeneratedGrammarParser();
public static GeneratedGrammarParser Instance => s_instance;
""");
builder.Append("\n");
builder.Append('\t').Append("public override ITransformer BeginTransformer => ")
.Append($"s_transformers[\"{beginState}\"];\n");
builder.Append('\t').Append("public override NonTerminator Begin => ")
.Append(begin.GenerateCode()).Append(";\n");
builder.Append("}\n");
return builder.ToString();
}
}

View File

@@ -0,0 +1,77 @@
using System.Text;
using Canon.Core.Abstractions;
using Canon.Core.GrammarParser;
namespace Canon.Generator.GrammarGenerator;
public class GeneratedTransformer : ITransformer
{
private readonly IDictionary<TerminatorBase, string> _shiftPointers;
public string Name { get; }
public IDictionary<Terminator, ReduceInformation> ReduceTable { get; }
public IDictionary<TerminatorBase, ITransformer> ShiftTable { get; }
public GeneratedTransformer(Dictionary<TerminatorBase, string> shiftTable,
Dictionary<Terminator, ReduceInformation> reduceTable, string name)
{
ReduceTable = reduceTable;
ShiftTable = new Dictionary<TerminatorBase, ITransformer>();
_shiftPointers = shiftTable;
Name = name;
}
public GeneratedTransformer()
{
ReduceTable = new Dictionary<Terminator, ReduceInformation>();
ShiftTable = new Dictionary<TerminatorBase, ITransformer>();
_shiftPointers = new Dictionary<TerminatorBase, string>();
Name = Guid.NewGuid().ToString();
}
public void ConstructShiftTable(Dictionary<string, GeneratedTransformer> transformers)
{
foreach (KeyValuePair<TerminatorBase,string> pair in _shiftPointers)
{
ShiftTable.Add(pair.Key, transformers[pair.Value]);
}
}
public override bool Equals(object? obj)
{
if (obj is not GeneratedTransformer other)
{
return false;
}
return Name == other.Name;
}
public override int GetHashCode() => Name.GetHashCode();
public string GenerateCode()
{
StringBuilder builder = new();
builder.Append("new GeneratedTransformer(new Dictionary<TerminatorBase, string>").Append("{");
foreach (KeyValuePair<TerminatorBase, ITransformer> pair in ShiftTable)
{
builder.Append($" {{ {pair.Key.GenerateCode()}, \"{pair.Value.Name}\"}},");
}
builder.Append("}, ");
builder.Append("new Dictionary<Terminator, ReduceInformation>{");
foreach (KeyValuePair<Terminator,ReduceInformation> pair in ReduceTable)
{
builder.Append($" {{ {pair.Key.GenerateCode()}, {pair.Value.GenerateCode()}}},");
}
builder.Append($" }}, \"{Name}\")");
return builder.ToString();
}
}

View File

@@ -0,0 +1,63 @@
using Canon.Core.Abstractions;
using Canon.Core.GrammarParser;
namespace Canon.Generator.GrammarGenerator;
public static class GrammarExtensions
{
public static GeneratedGrammarParser ToGeneratedGrammarParser(this Grammar grammar)
{
// 建立的逻辑和原始逻辑一致
Dictionary<LrState, GeneratedTransformer> transformers = [];
foreach (LrState state in grammar.Automation)
{
GeneratedTransformer transformer;
if (transformers.TryGetValue(state, out GeneratedTransformer? oldTransformer))
{
transformer = oldTransformer;
}
else
{
GeneratedTransformer generatedTransformer = new();
transformers.Add(state, generatedTransformer);
transformer = generatedTransformer;
}
foreach (Expression expression in state.Expressions)
{
if (expression.Pos == expression.Right.Count)
{
transformer.ReduceTable.TryAdd(expression.LookAhead, new ReduceInformation(
expression.Right.Count, expression.Left));
}
}
foreach (KeyValuePair<TerminatorBase,LrState> pair in state.Transformer)
{
GeneratedTransformer targetTransformer;
if (transformers.TryGetValue(pair.Value, out GeneratedTransformer? oldTargetTransformer))
{
targetTransformer = oldTargetTransformer;
}
else
{
GeneratedTransformer newTransformer = new();
transformers.Add(pair.Value, newTransformer);
targetTransformer = newTransformer;
}
transformer.ShiftTable.TryAdd(pair.Key, targetTransformer);
}
}
Dictionary<string, GeneratedTransformer> generatedTransformers = [];
foreach (GeneratedTransformer transformer in transformers.Values)
{
generatedTransformers.Add(transformer.Name, transformer);
}
return new GeneratedGrammarParser(generatedTransformers, transformers[grammar.BeginState].Name, grammar.Begin);
}
}

View File

@@ -0,0 +1,8 @@
using System.CommandLine;
using Canon.Generator.Extensions;
RootCommand rootCommand = new("Canon Compiler Source Generator");
rootCommand.AddGenerateCommand();
await rootCommand.InvokeAsync(args);