Skip to content

Instantly share code, notes, and snippets.

@stevekane
Created October 24, 2025 20:00
Show Gist options
  • Select an option

  • Save stevekane/5ddc6a31acd47819e10302fbfb098ece to your computer and use it in GitHub Desktop.

Select an option

Save stevekane/5ddc6a31acd47819e10302fbfb098ece to your computer and use it in GitHub Desktop.
// 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