-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
direct generator #3
Comments
Maybe possible by using POC codeusing Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using System;
var runner = new Runner();
Console.WriteLine(runner.Run("""
var x = 3;
var sum = 0;
foreach (var i in new[] {1,2,3,5,7,11,13})
{
_env.Append($"{x} × {i}\n");
sum += x * i;
}
_env.Append($"{sum}\n");
"""));
for (int i = 0; i < 10; i++)
{
Console.WriteLine(runner.Run("""
var r = new System.Random();
for (var i = 0; i < 10; ++i) _env.Append($"{r.Next():X}\n");
"""));
}
public class Runner
{
private readonly MemoryStream _memory = new(64 * 1024);
private readonly StringBuilder _environment = new(32* 1024);
public string Run(string source)
{
source = $$"""
public class Template
{
public static string Generate(System.Text.StringBuilder _env)
{
{{source}}
return _env.ToString();
}
}
""";
var s = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview));
using var c = Compile(s);
return Invoke(c.Assembly);
}
private string Invoke(Assembly a)
{
var t = a.GetType("Template")!;
var m = t.GetMethod("Generate")!;
_environment.Clear();
m.Invoke(null, new[] { _environment });
return _environment.ToString();
}
private Context Compile(SyntaxTree syntaxTree)
{
var references = AppDomain.CurrentDomain.GetAssemblies();
var mrefs = references
.Where(a => !string.IsNullOrEmpty(a.Location))
.Select(a => MetadataReference.CreateFromFile(a.Location));
var compilation = CSharpCompilation.Create(Path.GetRandomFileName(), new[] { syntaxTree }, mrefs, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var ms = _memory;
ms.Seek(0, SeekOrigin.Begin);
ms.SetLength(0);
var result = compilation.Emit(ms);
if (result.Success)
{
ms.Seek(0, SeekOrigin.Begin);
return new(ms);
}
else
{
throw new InvalidOperationException(string.Join("\n", result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error).Select(d => $"{d.Id}: {d.GetMessage()}")));
}
}
private struct Context : IDisposable
{
private readonly AssemblyLoadContext _context;
public Context(Stream assembly)
{
var context = new AssemblyLoadContext("", true);
context.LoadFromStream(assembly);
_context = context;
}
public Assembly Assembly => _context.Assemblies.First();
public void Dispose() => _context.Unload();
}
} POC codeusing Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
{ }
var text = """
$[
var names = new[] { "bool", "int", "string" };
var values = new[] { 2, 3, 5, 7, 11 };
]/
class X
{
$/ foreach (var x in names) {
public $x _$x { get; init; }
$/ }
public static IEnumerable<int> Values = new[] { ${string.Join(", ", values)} };
}
""";
var spans = Parser.Parse(text);
foreach (var s in spans) Console.WriteLine((s.Type, text[s.Range]));
Console.WriteLine("---------------------------");
var emitted = Emitter.Emit(text, spans);
Console.WriteLine(emitted);
Console.WriteLine("---------------------------");
var opt = ScriptOptions.Default.WithLanguageVersion(LanguageVersion.Preview);
var xxx = await CSharpScript.RunAsync(emitted, opt);
Console.WriteLine(xxx.ReturnValue);
enum SpanType
{
Raw,
Expression,
BraceExpression,
OneLineStatement,
Statements,
}
record struct Span(SpanType Type, Range Range);
class Emitter
{
public static string Emit(ReadOnlySpan<char> text, IEnumerable<Span> spans)
{
var sb = new StringBuilder();
sb.Append("""
var _env = new System.Text.StringBuilder();
""");
foreach (var s in spans)
{
var span = text[s.Range];
if (span.Length == 0) continue;
switch (s.Type)
{
case SpanType.Raw:
sb.Append($""""
_env.Append("""
{span}
""");
"""");
break;
case SpanType.Expression:
case SpanType.BraceExpression:
sb.Append($"""
_env.Append({span});
""");
break;
case SpanType.OneLineStatement:
case SpanType.Statements:
sb.Append(span);
break;
}
}
sb.Append("""
return _env.ToString();
""");
return sb.ToString();
}
}
class Parser
{
public static List<Span> Parse(string text)
{
var tokens = new List<Span>();
SpanType type = SpanType.Raw;
var span = text.AsSpan();
var start = 0;
while (true)
{
var (nextType, charsRead, range) = type switch
{
SpanType.OneLineStatement => ParseOneLineStatement(span),
SpanType.Statements => ParseStatements(span),
SpanType.Expression => ParseExpression(span),
SpanType.BraceExpression => ParseBraceExpression(span),
_ => ParseRaw(span),
};
tokens.Add(new Span(type, Shift(range, start)));
start += charsRead;
span = span[charsRead..];
if (nextType is not { } t) break;
type = t;
}
return tokens;
}
private static Range Shift(Range range, int offset) => (range.Start.Value + offset)..(range.End.Value + offset);
private static (SpanType? nextType, int charsRead, Range range) ParseBraceExpression(ReadOnlySpan<char> text)
{
if (text.Length == 0) return default;
int i;
for (i = 2; i < text.Length; i++)
{
if (text[i] is not (' ' or '\t' or '\r' or '\n')) break;
}
var tokenStart = i;
var tokenEnd = text.Length;
SpanType? type = null;
var nest = 0;
for (; i < text.Length; i++)
{
var c = text[i];
if (c == '{') nest++;
else if (c == '}')
{
nest--;
if (nest < 0)
{
type = SpanType.Raw;
tokenEnd = i;
break;
}
}
}
i++;
if (i < text.Length && text[i] == '/')
{
i++;
for (; i < text.Length; i++)
{
if (text[i] is not (' ' or '\t' or '\r' or '\n')) break;
}
}
return (type, i, tokenStart..tokenEnd);
}
private static (SpanType? nextType, int charsRead, Range range) ParseExpression(ReadOnlySpan<char> text)
{
if (text.Length == 0) return default;
int i;
for (i = 1; i < text.Length; i++)
{
if (text[i] is not (' ' or '\t' or '\r' or '\n')) break;
}
var tokenStart = i;
for (; i < text.Length; i++)
{
var c = text[i];
if (!char.IsAsciiLetter(c))
{
return (SpanType.Raw, i, tokenStart..i);
}
}
return (null, text.Length, tokenStart..text.Length);
}
private static (SpanType? nextType, int charsRead, Range range) ParseOneLineStatement(ReadOnlySpan<char> text)
{
if (text.Length == 0) return default;
for (var i = 0; i < text.Length; i++)
{
var c = text[i];
if (c == '\n')
{
return (SpanType.Raw, i + 1, 2..(i + 1));
}
}
return (null, text.Length, 2..text.Length);
}
private static (SpanType? nextType, int charsRead, Range range) ParseStatements(ReadOnlySpan<char> text)
{
if (text.Length == 0) return default;
var tokenEnd = text.Length;
SpanType? type = null;
var nest = 0;
int i;
for (i = 2; i < text.Length; i++)
{
var c = text[i];
if (c == '[') nest++;
else if (c == ']')
{
nest--;
if (nest < 0)
{
tokenEnd = i;
type = SpanType.Raw;
break;
}
}
}
i++;
if (i < text.Length && text[i] == '/')
{
i++;
for (; i < text.Length; i++)
{
if (text[i] is not (' ' or '\t' or '\r' or '\n')) break;
}
}
return (type, i, 2..tokenEnd);
}
private static (SpanType? nextType, int charsRead, Range range) ParseRaw(ReadOnlySpan<char> text)
{
if (text.Length == 0) return default;
for (var i = 0; i < text.Length; i++)
{
var c = text[i];
if (c == '$')
{
// EOF
if (i == text.Length) return (null, i, ..i);
var c1 = text[i + 1];
var nextType = c1 switch
{
'/' => SpanType.OneLineStatement,
'[' => SpanType.Statements,
'{' => SpanType.BraceExpression,
_ => SpanType.Expression,
};
return (nextType, i, ..i);
}
}
return (null, text.Length, ..text.Length);
}
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TextTemplatePreprocessor
is something like TextTemplatingFilePreprocessor in T4 template engine.I need also TextTemplatingFileGenerator in T4.
The text was updated successfully, but these errors were encountered: