Created
October 24, 2025 20:00
-
-
Save stevekane/5ddc6a31acd47819e10302fbfb098ece to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Transpiler.cs | |
| // dotnet add package Microsoft.CodeAnalysis.CSharp | |
| using System; | |
| using System.Collections.Generic; | |
| using System.IO; | |
| using System.Linq; | |
| using System.Reflection; | |
| using Microsoft.CodeAnalysis; | |
| using Microsoft.CodeAnalysis.CSharp; | |
| using Microsoft.CodeAnalysis.CSharp.Syntax; | |
| using Microsoft.CodeAnalysis.Emit; | |
| using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | |
| namespace ProcessDSL_Transpiler_FinalFix | |
| { | |
| // ---------------- 1) Source AST ---------------- | |
| abstract class Node { } | |
| sealed class ProgramNode : Node { public readonly List<Node> Decls = new(); } | |
| sealed class ChannelDecl : Node | |
| { | |
| public readonly string Name; | |
| public ChannelDecl(string name) { Name = name; } | |
| } | |
| sealed class LetDecl : Node | |
| { | |
| public readonly string Name; | |
| public readonly Expr Value; | |
| public LetDecl(string name, Expr value) { Name = name; Value = value; } | |
| } | |
| abstract class Expr : Node { public abstract string Render(); } | |
| // c!value . then | |
| sealed class Send : Expr | |
| { | |
| public readonly string Channel; public readonly Expr Value; public readonly Expr Then; | |
| public Send(string ch, Expr v, Expr then) { Channel = ch; Value = v; Then = then; } | |
| public override string Render() => $"{Channel}!{Value.Render()} . {Then.Render()}"; | |
| } | |
| // c?v . body | |
| sealed class Recv : Expr | |
| { | |
| public readonly string Channel; public readonly string Var; public readonly Expr Body; | |
| public Recv(string ch, string v, Expr body) { Channel = ch; Var = v; Body = body; } | |
| public override string Render() => $"{Channel}?{Var} . {Body.Render()}"; | |
| } | |
| sealed class AllExpr : Expr | |
| { | |
| public readonly List<Expr> Children = new(); | |
| public AllExpr(params Expr[] kids) { Children.AddRange(kids); } | |
| public override string Render() => "all | " + string.Join(" | ", Children.Select(c => c.Render())); | |
| } | |
| sealed class IntLit : Expr { public readonly int V; public IntLit(int v) { V = v; } public override string Render() => V.ToString(); } | |
| sealed class VarRef : Expr { public readonly string Name; public VarRef(string n) { Name = n; } public override string Render() => Name; } | |
| sealed class Add : Expr { public readonly Expr L, R; public Add(Expr l, Expr r) { L = l; R = r; } public override string Render() => $"{L.Render()} + {R.Render()}"; } | |
| // ---------------- 2) Transpiler ---------------- | |
| sealed class Transpiler | |
| { | |
| private int _classId = 0; | |
| private string NewClassName(string hint) => $"P{++_classId}_{hint}"; | |
| private string NewTemp(string hint) => $"_{hint}_{_classId}_{Guid.NewGuid().ToString("N")[..6]}"; | |
| private static readonly TypeSyntax IntT = PredefinedType(Token(SyntaxKind.IntKeyword)); | |
| private static readonly TypeSyntax VoidT = PredefinedType(Token(SyntaxKind.VoidKeyword)); | |
| public string Transpile(ProgramNode prog) | |
| { | |
| var cu = CompilationUnit() | |
| .AddUsings( | |
| UsingDirective(IdentifierName("System")), | |
| UsingDirective(IdentifierName("System.Collections.Generic")) | |
| ); | |
| // ---- Runtime (no nullable annotations) ---- | |
| cu = cu.AddMembers(ParseMemberDeclaration(@" | |
| public struct Unit {} | |
| ")!); | |
| cu = cu.AddMembers(ParseMemberDeclaration(@" | |
| public interface IProcessBase { bool IsCompleted { get; } void Step(); } | |
| ")!); | |
| cu = cu.AddMembers(ParseMemberDeclaration(@" | |
| public interface IProcess<T> : IProcessBase { T Result { get; } } | |
| ")!); | |
| cu = cu.AddMembers(ParseMemberDeclaration(@" | |
| public sealed class Scheduler | |
| { | |
| private readonly List<IProcessBase> _all = new List<IProcessBase>(); | |
| public void Register(IProcessBase p) { _all.Add(p); } | |
| public bool Tick() | |
| { | |
| bool any=false; | |
| for (int i=0;i<_all.Count;i++) | |
| { | |
| var p=_all[i]; | |
| if(!p.IsCompleted){ p.Step(); any=true; } | |
| } | |
| return any; | |
| } | |
| public bool AllCompleted | |
| { | |
| get | |
| { | |
| for (int i=0;i<_all.Count;i++) if(!_all[i].IsCompleted) return false; | |
| return true; | |
| } | |
| } | |
| } | |
| ")!); | |
| cu = cu.AddMembers(ParseMemberDeclaration(@" | |
| public static class Debug { public static void Log(object x){ Console.WriteLine(x); } } | |
| ")!); | |
| cu = cu.AddMembers(ParseMemberDeclaration(@" | |
| public sealed class Channel<T> | |
| { | |
| private bool hasValue; | |
| private T value; | |
| private bool receiverReady; | |
| public bool TrySend(T v) | |
| { | |
| if (receiverReady && !hasValue) | |
| { | |
| value = v; hasValue = true; | |
| receiverReady = false; | |
| return true; | |
| } | |
| value = v; // staged until receiver is ready on a later tick | |
| return false; | |
| } | |
| public bool TryReceive(out T v) | |
| { | |
| if (hasValue) | |
| { | |
| v = value; | |
| hasValue = false; | |
| return true; | |
| } | |
| receiverReady = true; | |
| v = default(T); | |
| return false; | |
| } | |
| } | |
| ")!); | |
| cu = cu.AddMembers(ParseMemberDeclaration(@" | |
| public sealed class AllProcess<T1,T2> : IProcess<(T1,T2)> | |
| { | |
| private readonly IProcess<T1> _a; | |
| private readonly IProcess<T2> _b; | |
| private bool _done; | |
| private (T1,T2) _res; | |
| public AllProcess(IProcess<T1> a, IProcess<T2> b){ _a=a; _b=b; } | |
| public bool IsCompleted { get { return _done; } } | |
| public void Step() | |
| { | |
| if (_done) return; | |
| if (_a.IsCompleted && _b.IsCompleted) | |
| { | |
| _res = (_a.Result, _b.Result); | |
| _done = true; | |
| } | |
| } | |
| public (T1,T2) Result { get { return _res; } } | |
| } | |
| ")!); | |
| cu = cu.AddMembers(ParseMemberDeclaration(@" | |
| public sealed class AnyProcess<T1,T2> : IProcess<object> | |
| { | |
| private readonly IProcess<T1> _a; | |
| private readonly IProcess<T2> _b; | |
| private bool _done; | |
| private object _res; | |
| public AnyProcess(IProcess<T1> a, IProcess<T2> b){ _a=a; _b=b; } | |
| public bool IsCompleted { get { return _done; } } | |
| public void Step() | |
| { | |
| if (_done) return; | |
| if (_a.IsCompleted){ _res = (object)_a.Result; _done = true; return; } | |
| if (_b.IsCompleted){ _res = (object)_b.Result; _done = true; return; } | |
| } | |
| public object Result { get { return _res; } } | |
| } | |
| ")!); | |
| // ---- Top-level wiring ---- | |
| var topFields = new List<MemberDeclarationSyntax>(); | |
| var ctorStmts = new List<StatementSyntax>(); | |
| topFields.Add(ParseMemberDeclaration("private readonly Scheduler _sched;")!); | |
| ctorStmts.Add(ParseStatement("_sched = sched;")); | |
| // We WILL NOT declare 'r' here to avoid duplicates. | |
| string allVarName = null; | |
| string leftResType = null; | |
| string rightResType = null; | |
| foreach (var node in prog.Decls) | |
| { | |
| switch (node) | |
| { | |
| case ChannelDecl ch: | |
| topFields.Add(ParseMemberDeclaration($"private Channel<int> {ch.Name};")!); | |
| ctorStmts.Add(ParseStatement($"{ch.Name} = new Channel<int>();")); | |
| break; | |
| case LetDecl let when let.Value is AllExpr all: | |
| if (all.Children.Count != 2) | |
| throw new NotSupportedException("Demo supports exactly two children in 'all'."); | |
| // left child class & instance | |
| var leftClass = EmitProcessClass(all.Children[0], out var leftClassName, out leftResType); | |
| cu = cu.AddMembers(leftClass); | |
| var leftVar = NewTemp("left"); | |
| ctorStmts.Add(ParseStatement($"var {leftVar} = {NewProcessInstance(all.Children[0], leftClassName)};")); | |
| ctorStmts.Add(ParseStatement($"_sched.Register({leftVar});")); | |
| // right child class & instance | |
| var rightClass = EmitProcessClass(all.Children[1], out var rightClassName, out rightResType); | |
| cu = cu.AddMembers(rightClass); | |
| var rightVar = NewTemp("right"); | |
| ctorStmts.Add(ParseStatement($"var {rightVar} = {NewProcessInstance(all.Children[1], rightClassName)};")); | |
| ctorStmts.Add(ParseStatement($"_sched.Register({rightVar});")); | |
| allVarName = let.Name; | |
| // Constructor will assign: r = new AllProcess<...>(left,right); and register it. | |
| ctorStmts.Add(ParseStatement($"{allVarName} = new AllProcess<{leftResType},{rightResType}>({leftVar},{rightVar});")); | |
| ctorStmts.Add(ParseStatement($"_sched.Register({allVarName});")); | |
| break; | |
| } | |
| } | |
| var top = BuildTopLevel(topFields, ctorStmts, allVarName, leftResType, rightResType); | |
| cu = cu.AddMembers(top); | |
| cu = cu.AddMembers(ParseMemberDeclaration(@" | |
| public static class Program | |
| { | |
| public static void Main() | |
| { | |
| var sched = new Scheduler(); | |
| var mainProc = new TopLevelProcess(sched); | |
| sched.Register(mainProc); | |
| for (int t=0;t<128 && !sched.AllCompleted;t++) sched.Tick(); | |
| } | |
| } | |
| ")!); | |
| return cu.NormalizeWhitespace().ToFullString(); | |
| } | |
| // ---- process classes for Send/Recv ---- | |
| ClassDeclarationSyntax EmitProcessClass(Expr e, out string className, out string resultType) | |
| { | |
| if (e is Send s) | |
| { | |
| className = NewClassName("Send"); resultType = "int"; | |
| return | |
| ClassDeclaration(className) | |
| .WithLeadingTrivia(TriviaList(Comment($"// source: {s.Render()}"))) | |
| .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.SealedKeyword)) | |
| .WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(ParseTypeName("IProcess<int>"))))) | |
| .AddMembers( | |
| ParseMemberDeclaration("private enum State { Start, Sent, Done }")!, | |
| ParseMemberDeclaration("private State _st = State.Start;")!, | |
| ParseMemberDeclaration("private readonly Channel<int> _c;")!, | |
| ParseMemberDeclaration("private readonly int _value;")!, | |
| ParseMemberDeclaration("private int _result;")!, | |
| ParseMemberDeclaration("private bool _done;")!, | |
| ConstructorDeclaration(className) | |
| .AddModifiers(Token(SyntaxKind.PublicKeyword)) | |
| .WithParameterList(ParameterList(SeparatedList(new[]{ | |
| Parameter(Identifier("c")).WithType(ParseTypeName("Channel<int>")), | |
| Parameter(Identifier("value")).WithType(IntT), | |
| Parameter(Identifier("thenValue")).WithType(IntT) | |
| }))) | |
| .WithBody(Block( | |
| ParseStatement("_c = c;"), | |
| ParseStatement("_value = value;"), | |
| ParseStatement("_result = thenValue;") | |
| )), | |
| ParseMemberDeclaration("public bool IsCompleted { get { return _done; } }")!, | |
| ParseMemberDeclaration("public int Result { get { return _result; } }")!, | |
| MethodDeclaration(VoidT, "Step") | |
| .AddModifiers(Token(SyntaxKind.PublicKeyword)) | |
| .WithBody(Block( | |
| ParseStatement("if (_done) return;"), | |
| ParseStatement("switch(_st){"), | |
| ParseStatement(" case State.Start: if (_c.TrySend(_value)) { _st = State.Sent; } return;"), | |
| ParseStatement(" case State.Sent: _st = State.Done; _done = true; return;"), | |
| ParseStatement("}") | |
| )) | |
| ); | |
| } | |
| if (e is Recv r) | |
| { | |
| className = NewClassName("Recv"); resultType = "int"; | |
| string bodyExpr = CompileValueExpr(r.Body, r.Var); | |
| return | |
| ClassDeclaration(className) | |
| .WithLeadingTrivia(TriviaList(Comment($"// source: {r.Render()}"))) | |
| .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.SealedKeyword)) | |
| .WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(ParseTypeName("IProcess<int>"))))) | |
| .AddMembers( | |
| ParseMemberDeclaration("private enum State { Waiting, Received, Done }")!, | |
| ParseMemberDeclaration("private State _st = State.Waiting;")!, | |
| ParseMemberDeclaration("private readonly Channel<int> _c;")!, | |
| ParseMemberDeclaration("private int _v;")!, | |
| ParseMemberDeclaration("private int _result;")!, | |
| ParseMemberDeclaration("private bool _done;")!, | |
| ConstructorDeclaration(className) | |
| .AddModifiers(Token(SyntaxKind.PublicKeyword)) | |
| .WithParameterList(ParameterList(SingletonSeparatedList( | |
| Parameter(Identifier("c")).WithType(ParseTypeName("Channel<int>")) | |
| ))) | |
| .WithBody(Block(ParseStatement("_c = c;"))), | |
| ParseMemberDeclaration("public bool IsCompleted { get { return _done; } }")!, | |
| ParseMemberDeclaration("public int Result { get { return _result; } }")!, | |
| MethodDeclaration(VoidT, "Step") | |
| .AddModifiers(Token(SyntaxKind.PublicKeyword)) | |
| .WithBody(Block( | |
| ParseStatement("if (_done) return;"), | |
| ParseStatement("switch(_st){"), | |
| ParseStatement(" case State.Waiting: if (_c.TryReceive(out _v)) { _st = State.Received; } return;"), | |
| ParseStatement($" case State.Received: _result = {bodyExpr}; _st = State.Done; _done = true; return;"), | |
| ParseStatement("}") | |
| )) | |
| ); | |
| } | |
| throw new NotSupportedException($"Unsupported process expr: {e.GetType().Name}"); | |
| } | |
| string NewProcessInstance(Expr e, string className) => e switch | |
| { | |
| Send s => $"new {className}({s.Channel}, {CompileValueExpr(s.Value, null)}, {CompileValueExpr(s.Then, null)})", | |
| Recv r => $"new {className}({r.Channel})", | |
| _ => throw new NotSupportedException() | |
| }; | |
| string CompileValueExpr(Expr e, string recvVar) => e switch | |
| { | |
| IntLit i => i.V.ToString(), | |
| VarRef v => (recvVar != null && v.Name == recvVar) ? "_v" : v.Name, | |
| Add a => $"({CompileValueExpr(a.L, recvVar)} + {CompileValueExpr(a.R, recvVar)})", | |
| _ => throw new NotSupportedException("Only Int, Var, Add supported for value expressions") | |
| }; | |
| // Declare 'r' ONLY here (NOT earlier), so there is no duplicate. | |
| ClassDeclarationSyntax BuildTopLevel( | |
| List<MemberDeclarationSyntax> fields, | |
| List<StatementSyntax> ctorStmts, | |
| string allVarName, | |
| string leftResType, | |
| string rightResType | |
| ) | |
| { | |
| var cls = | |
| ClassDeclaration("TopLevelProcess") | |
| .WithLeadingTrivia(TriviaList(Comment("// source: top-level program"))) | |
| .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.SealedKeyword)) | |
| .WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(ParseTypeName("IProcess<Unit>"))))) | |
| .AddMembers(fields.ToArray()) | |
| .AddMembers(ParseMemberDeclaration($"private AllProcess<{leftResType},{rightResType}> {allVarName};")!) // <- only here | |
| .AddMembers(ParseMemberDeclaration("private bool _done;")!) | |
| .AddMembers( | |
| ConstructorDeclaration("TopLevelProcess") | |
| .AddModifiers(Token(SyntaxKind.PublicKeyword)) | |
| .AddParameterListParameters(Parameter(Identifier("sched")).WithType(IdentifierName("Scheduler"))) | |
| .WithBody(Block(ctorStmts)) | |
| ) | |
| .AddMembers(ParseMemberDeclaration("public bool IsCompleted { get { return _done; } }")!) | |
| .AddMembers(ParseMemberDeclaration("public Unit Result { get { return new Unit(); } }")!) | |
| .AddMembers( | |
| MethodDeclaration(VoidT, "Step") | |
| .AddModifiers(Token(SyntaxKind.PublicKeyword)) | |
| .WithBody(Block(ParseStatement($"if ({allVarName}.IsCompleted) {{ var tup = {allVarName}.Result; Debug.Log($\"r = ({{tup.Item1}}, {{tup.Item2}})\"); _done = true; }}"))) | |
| ); | |
| return cls; | |
| } | |
| } | |
| // ---------------- 3) Driver: build AST, transpile, write, compile+run ---------------- | |
| static class Driver | |
| { | |
| static void Main() | |
| { | |
| // Source program: | |
| // channel c | |
| // let r = all | |
| // | c!2 . 1 | |
| // | c?v . v + 1 | |
| // Debug.Log(r) | |
| // prints (1,3) | |
| var prog = new ProgramNode(); | |
| prog.Decls.Add(new ChannelDecl("c")); | |
| var send = new Send("c", new IntLit(2), new IntLit(1)); | |
| var recv = new Recv("c", "v", new Add(new VarRef("v"), new IntLit(1))); | |
| prog.Decls.Add(new LetDecl("r", new AllExpr(send, recv))); | |
| var t = new Transpiler(); | |
| var code = t.Transpile(prog); | |
| // Write file for inspection | |
| var outPath = Path.Combine(Environment.CurrentDirectory, "GeneratedProgram.cs"); | |
| File.WriteAllText(outPath, code); | |
| Console.WriteLine($"Wrote: {outPath}"); | |
| // In-memory compile + run (Option 2) | |
| var tree = CSharpSyntaxTree.ParseText(code); | |
| // Robust reference set (System.Runtime + friends) | |
| string coreDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); | |
| MetadataReference Ref(string p) => MetadataReference.CreateFromFile(p); | |
| var refs = new List<MetadataReference> | |
| { | |
| Ref(typeof(object).Assembly.Location), // System.Private.CoreLib | |
| Ref(typeof(Console).Assembly.Location), // System.Console | |
| Ref(typeof(List<>).Assembly.Location), // System.Collections | |
| Ref(typeof(Enumerable).Assembly.Location), // System.Linq | |
| Ref(Path.Combine(coreDir, "System.Runtime.dll")), | |
| Ref(Path.Combine(coreDir, "System.Console.dll")), | |
| Ref(Path.Combine(coreDir, "System.Collections.dll")), | |
| Ref(Path.Combine(coreDir, "System.Linq.dll")) | |
| }.Distinct().ToList(); | |
| var comp = CSharpCompilation.Create( | |
| "GeneratedProgram", | |
| new[] { tree }, | |
| refs, | |
| new CSharpCompilationOptions(OutputKind.ConsoleApplication, optimizationLevel: OptimizationLevel.Release) | |
| ); | |
| using var pe = new MemoryStream(); | |
| EmitResult result = comp.Emit(pe); | |
| if (!result.Success) | |
| { | |
| foreach (var d in result.Diagnostics) Console.WriteLine(d.ToString()); | |
| return; | |
| } | |
| pe.Position = 0; | |
| var asm = Assembly.Load(pe.ToArray()); | |
| var ep = asm.EntryPoint!; | |
| var epParams = ep.GetParameters(); | |
| object? instance = ep.IsStatic ? null : (ep.DeclaringType != null ? Activator.CreateInstance(ep.DeclaringType) : null); | |
| if (epParams.Length == 0) | |
| { | |
| ep.Invoke(instance, null); | |
| } | |
| else | |
| { | |
| ep.Invoke(instance, new object?[] { Array.Empty<string>() }); | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment