Skip to content

Commit 9a3c390

Browse files
authored
Merge pull request #9 from jonathanvdc/copilot/teach-dialectsourceemitter-synthesize-cst
Emit concrete OperationBodySyntax subclass per operation with declarative assembly format
2 parents 2e1a501 + 7e28c50 commit 9a3c390

3 files changed

Lines changed: 400 additions & 1 deletion

File tree

src/MLIR.Generators/DialectSourceEmitter.cs

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
namespace MLIR.Generators;
22

3+
using System.Collections.Generic;
34
using System.Globalization;
45
using System.Text;
56
using MLIR.ODS.Model;
7+
using MLIR.ODS.Model.AssemblyFormat;
8+
using MLIR.Text;
69

710
internal static class DialectSourceEmitter
811
{
@@ -29,6 +32,12 @@ public static string GenerateDialectSource(DialectModel dialect)
2932
AppendOperationClass(builder, operation);
3033
builder.AppendLine();
3134

35+
if (operation.AssemblyFormat != null)
36+
{
37+
AppendOperationBodySyntaxClass(builder, operation);
38+
builder.AppendLine();
39+
}
40+
3241
if (operation.HasCustomAssemblyFormat)
3342
{
3443
AppendAssemblyFormatClass(builder, operation);
@@ -169,6 +178,308 @@ private static void AppendOperationClass(StringBuilder builder, OperationModel o
169178
builder.AppendLine("}");
170179
}
171180

181+
private static void AppendOperationBodySyntaxClass(StringBuilder builder, OperationModel operation)
182+
{
183+
var className = DialectGeneratorNaming.GetOperationClassName(operation);
184+
var assemblyFormat = operation.AssemblyFormat!;
185+
var fields = ComputeBodySyntaxFields(operation, assemblyFormat);
186+
187+
builder.AppendLine("public sealed class " + className + "BodySyntax : OperationBodySyntax");
188+
builder.AppendLine("{");
189+
190+
// Constructor
191+
builder.Append(" public " + className + "BodySyntax(");
192+
for (var i = 0; i < fields.Count; i++)
193+
{
194+
if (i > 0)
195+
{
196+
builder.Append(", ");
197+
}
198+
199+
builder.Append(fields[i].CsType + " " + LowerFirst(fields[i].Name));
200+
}
201+
202+
builder.AppendLine(")");
203+
builder.AppendLine(" {");
204+
foreach (var field in fields)
205+
{
206+
builder.AppendLine(" " + field.Name + " = " + LowerFirst(field.Name) + ";");
207+
}
208+
209+
builder.AppendLine(" }");
210+
211+
if (fields.Count > 0)
212+
{
213+
builder.AppendLine();
214+
foreach (var field in fields)
215+
{
216+
builder.AppendLine(" public " + field.CsType + " " + field.Name + " { get; }");
217+
}
218+
}
219+
220+
builder.AppendLine();
221+
builder.AppendLine(" public override bool TryGetGenericBody(out GenericOperationBodySyntax? genericBody)");
222+
builder.AppendLine(" {");
223+
builder.AppendLine(" genericBody = null;");
224+
builder.AppendLine(" return false;");
225+
builder.AppendLine(" }");
226+
builder.AppendLine();
227+
builder.AppendLine(" public override void WriteTo(Text.SyntaxWriter writer, int indentLevel, System.Action<Text.SyntaxWriter, RegionSyntax, int> writeRegion)");
228+
builder.AppendLine(" {");
229+
foreach (var field in fields)
230+
{
231+
builder.Append(field.WriteToCode);
232+
}
233+
234+
builder.AppendLine(" }");
235+
builder.AppendLine("}");
236+
}
237+
238+
private static IReadOnlyList<BodySyntaxField> ComputeBodySyntaxFields(OperationModel operation, AssemblyFormatModel assemblyFormat)
239+
{
240+
var fields = new List<BodySyntaxField>();
241+
var usedNames = new HashSet<string>(StringComparer.Ordinal);
242+
243+
foreach (var element in assemblyFormat.Elements)
244+
{
245+
AppendBodySyntaxFields(fields, usedNames, element, operation);
246+
}
247+
248+
return fields;
249+
}
250+
251+
private static void AppendBodySyntaxFields(List<BodySyntaxField> fields, HashSet<string> usedNames, Element element, OperationModel operation)
252+
{
253+
switch (element)
254+
{
255+
case LiteralChunk literal:
256+
foreach (var lit in literal.Value)
257+
{
258+
switch (lit)
259+
{
260+
case PunctuationLiteral punc:
261+
{
262+
var name = MakeUnique(GetPunctuationFieldName(punc.TokenKind), usedNames);
263+
fields.Add(new BodySyntaxField(name, "SyntaxToken",
264+
" writer.WriteToken(" + name + ", string.Empty);\n"));
265+
break;
266+
}
267+
268+
case KeywordLiteral kw:
269+
{
270+
var name = MakeUnique(DialectGeneratorNaming.ToPascalCase(kw.Spelling) + "Keyword", usedNames);
271+
fields.Add(new BodySyntaxField(name, "SyntaxToken",
272+
" writer.WriteToken(" + name + ", \" \");\n"));
273+
break;
274+
}
275+
276+
// WhitespaceLiteral, NewlineLiteral, EmptyLiteral → no field; spacing is in stored trivia
277+
}
278+
}
279+
280+
break;
281+
282+
case VariableChunk variable:
283+
{
284+
var pascalName = DialectGeneratorNaming.ToPascalCase(variable.Name);
285+
if (ContainsName(operation.Attributes, variable.Name))
286+
{
287+
var name = MakeUnique(pascalName, usedNames);
288+
fields.Add(new BodySyntaxField(name, "AttributeValueSyntax",
289+
" " + name + ".WriteTo(writer, \" \");\n"));
290+
}
291+
else
292+
{
293+
// Operand, result variable, or unknown → SyntaxToken
294+
var name = MakeUnique(pascalName, usedNames);
295+
fields.Add(new BodySyntaxField(name, "SyntaxToken",
296+
" writer.WriteToken(" + name + ", \" \");\n"));
297+
}
298+
299+
break;
300+
}
301+
302+
case AttrDictDirectiveChunk _:
303+
case AttrDictWithKeywordDirectiveChunk _:
304+
{
305+
var name = MakeUnique("AttrDict", usedNames);
306+
fields.Add(new BodySyntaxField(name, "DelimitedSyntaxList<NamedAttributeSyntax>",
307+
GenerateDelimitedNamedAttributeWriteTo(name)));
308+
break;
309+
}
310+
311+
case PropDictDirectiveChunk _:
312+
{
313+
var name = MakeUnique("PropDict", usedNames);
314+
fields.Add(new BodySyntaxField(name, "DelimitedSyntaxList<NamedAttributeSyntax>",
315+
GenerateDelimitedNamedAttributeWriteTo(name)));
316+
break;
317+
}
318+
319+
case RegionsDirectiveChunk _:
320+
{
321+
var name = MakeUnique("Regions", usedNames);
322+
fields.Add(new BodySyntaxField(name, "IReadOnlyList<RegionSyntax>",
323+
" foreach (var region in " + name + ")\n" +
324+
" {\n" +
325+
" writeRegion(writer, region, indentLevel);\n" +
326+
" }\n"));
327+
break;
328+
}
329+
330+
case TypeDirectiveChunk typeDir:
331+
{
332+
var baseName = typeDir.Operand is VariableOperand varOp
333+
? DialectGeneratorNaming.ToPascalCase(varOp.Name) + "Type"
334+
: "Type";
335+
var name = MakeUnique(baseName, usedNames);
336+
fields.Add(new BodySyntaxField(name, "TypeSyntax",
337+
" " + name + ".WriteTo(writer, \" \");\n"));
338+
break;
339+
}
340+
341+
case SuccessorsDirectiveChunk _:
342+
{
343+
var name = MakeUnique("Successors", usedNames);
344+
fields.Add(new BodySyntaxField(name, "DelimitedSyntaxList<SyntaxToken>",
345+
GenerateDelimitedTokenWriteTo(name)));
346+
break;
347+
}
348+
349+
case OperandsDirectiveChunk _:
350+
{
351+
var name = MakeUnique("Operands", usedNames);
352+
fields.Add(new BodySyntaxField(name, "DelimitedSyntaxList<SyntaxToken>",
353+
GenerateDelimitedTokenWriteTo(name)));
354+
break;
355+
}
356+
357+
// OptionalGroup, OilistDirectiveChunk, CustomDirectiveChunk, FunctionalTypeDirectiveChunk,
358+
// QualifiedDirectiveChunk, RefDirectiveChunk, ResultsDirectiveChunk → not stored in this CST class
359+
}
360+
}
361+
362+
private static string GenerateDelimitedNamedAttributeWriteTo(string fieldName)
363+
{
364+
return
365+
" if (" + fieldName + ".OpenToken != null)\n" +
366+
" {\n" +
367+
" writer.WriteToken(" + fieldName + ".OpenToken.Value, \" \");\n" +
368+
" for (var i = 0; i < " + fieldName + ".Count; i++)\n" +
369+
" {\n" +
370+
" if (i > 0)\n" +
371+
" {\n" +
372+
" writer.WriteToken(" + fieldName + ".SeparatorTokens[i - 1], string.Empty);\n" +
373+
" }\n" +
374+
" " + fieldName + "[i].WriteTo(writer, i > 0 ? \" \" : string.Empty);\n" +
375+
" }\n" +
376+
" writer.WriteToken(" + fieldName + ".CloseToken!.Value, string.Empty);\n" +
377+
" }\n";
378+
}
379+
380+
private static string GenerateDelimitedTokenWriteTo(string fieldName)
381+
{
382+
return
383+
" if (" + fieldName + ".OpenToken != null)\n" +
384+
" {\n" +
385+
" writer.WriteToken(" + fieldName + ".OpenToken.Value, \" \");\n" +
386+
" for (var i = 0; i < " + fieldName + ".Count; i++)\n" +
387+
" {\n" +
388+
" if (i > 0)\n" +
389+
" {\n" +
390+
" writer.WriteToken(" + fieldName + ".SeparatorTokens[i - 1], string.Empty);\n" +
391+
" }\n" +
392+
" writer.WriteToken(" + fieldName + "[i], i > 0 ? \" \" : string.Empty);\n" +
393+
" }\n" +
394+
" writer.WriteToken(" + fieldName + ".CloseToken!.Value, string.Empty);\n" +
395+
" }\n";
396+
}
397+
398+
private static string GetPunctuationFieldName(TokenKind tokenKind)
399+
{
400+
return tokenKind switch
401+
{
402+
TokenKind.Comma => "CommaToken",
403+
TokenKind.LParen => "LParenToken",
404+
TokenKind.RParen => "RParenToken",
405+
TokenKind.LBracket => "LBracketToken",
406+
TokenKind.RBracket => "RBracketToken",
407+
TokenKind.LBrace => "LBraceToken",
408+
TokenKind.RBrace => "RBraceToken",
409+
TokenKind.Arrow => "ArrowToken",
410+
TokenKind.Colon => "ColonToken",
411+
TokenKind.Equal => "EqualToken",
412+
TokenKind.LessThan => "LessThanToken",
413+
TokenKind.GreaterThan => "GreaterThanToken",
414+
TokenKind.Question => "QuestionToken",
415+
TokenKind.Star => "StarToken",
416+
TokenKind.Plus => "PlusToken",
417+
TokenKind.Minus => "MinusToken",
418+
TokenKind.Dot => "DotToken",
419+
TokenKind.At => "AtToken",
420+
TokenKind.Hash => "HashToken",
421+
_ => "Token",
422+
};
423+
}
424+
425+
private static string MakeUnique(string baseName, HashSet<string> used)
426+
{
427+
if (used.Add(baseName))
428+
{
429+
return baseName;
430+
}
431+
432+
for (var i = 2; ; i++)
433+
{
434+
var candidate = baseName + i.ToString(CultureInfo.InvariantCulture);
435+
if (used.Add(candidate))
436+
{
437+
return candidate;
438+
}
439+
}
440+
}
441+
442+
private static string LowerFirst(string name)
443+
{
444+
if (name.Length == 0)
445+
{
446+
return name;
447+
}
448+
449+
return char.ToLowerInvariant(name[0]) + name.Substring(1);
450+
}
451+
452+
private static bool ContainsName(IReadOnlyList<string> names, string name)
453+
{
454+
foreach (var n in names)
455+
{
456+
if (string.Equals(n, name, StringComparison.Ordinal))
457+
{
458+
return true;
459+
}
460+
}
461+
462+
return false;
463+
}
464+
465+
private sealed class BodySyntaxField
466+
{
467+
public BodySyntaxField(string name, string csType, string writeToCode)
468+
{
469+
Name = name;
470+
CsType = csType;
471+
WriteToCode = writeToCode;
472+
}
473+
474+
public string Name { get; }
475+
public string CsType { get; }
476+
477+
/// <summary>
478+
/// C# code (indented for the WriteTo body, ending with a newline) that writes this field.
479+
/// </summary>
480+
public string WriteToCode { get; }
481+
}
482+
172483
private static void AppendAssemblyFormatClass(StringBuilder builder, OperationModel operation)
173484
{
174485
var className = DialectGeneratorNaming.GetOperationClassName(operation);

src/MLIR/Syntax/NamedAttributeSyntax.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ public bool TryGetRawValue(out RawSyntaxText? rawValue)
5656
/// </summary>
5757
public RawSyntaxText RawValue => ValueSyntax.GetRawText();
5858

59-
internal void WriteTo(SyntaxWriter writer, string defaultLeadingTrivia)
59+
/// <summary>
60+
/// Writes the named attribute to the supplied syntax writer.
61+
/// </summary>
62+
/// <param name="writer">The syntax writer to write to.</param>
63+
/// <param name="defaultLeadingTrivia">The fallback leading trivia for the name token.</param>
64+
public void WriteTo(SyntaxWriter writer, string defaultLeadingTrivia)
6065
{
6166
writer.WriteToken(NameToken, defaultLeadingTrivia);
6267
writer.WriteToken(EqualsToken, " ");

0 commit comments

Comments
 (0)