Skip to content
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

Open
ufcpp opened this issue Feb 15, 2021 · 1 comment
Open

direct generator #3

ufcpp opened this issue Feb 15, 2021 · 1 comment

Comments

@ufcpp
Copy link
Owner

ufcpp commented Feb 15, 2021

TextTemplatePreprocessor is something like TextTemplatingFilePreprocessor in T4 template engine.
I need also TextTemplatingFileGenerator in T4.

@ufcpp
Copy link
Owner Author

ufcpp commented Aug 29, 2022

Maybe possible by using CSharpScript.RunAsync.

POC code
using 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 code
using 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
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant