Add canonical instance encoding for constants

This commit is contained in:
Yoshi Askharoun
2024-07-07 15:34:27 -05:00
parent 7d408659dc
commit 3192685f82
5 changed files with 190 additions and 129 deletions
+74 -61
View File
@@ -14,6 +14,7 @@ public class Disassembler
private readonly MarkupLoadResult _loadResult; private readonly MarkupLoadResult _loadResult;
private readonly Dictionary<string, string> _importedUris; private readonly Dictionary<string, string> _importedUris;
private readonly Dictionary<uint, List<Label>> _offsetLabelMap = new(); private readonly Dictionary<uint, List<Label>> _offsetLabelMap = new();
private static readonly TypeSchema _stringTypeSchema = UIXTypes.MapIDToType(UIXTypeID.String);
private Disassembler(MarkupLoadResult loadResult) private Disassembler(MarkupLoadResult loadResult)
{ {
@@ -224,70 +225,10 @@ public class Disassembler
} }
} }
var stringTypeSchema = UIXTypes.MapIDToType(UIXTypeID.String);
foreach (var (c, typeSchema, constantValue) in constants) foreach (var (c, typeSchema, constantValue) in constants)
{ {
var constantName = $"const{c:D}"; var constantName = $"const{c:D}";
QualifiedTypeName qualifiedTypeName = GetQualifiedName(typeSchema); yield return EncodeConstant(constantValue, constantName, typeSchema);
if (constantValue is IStringEncodable encodable)
{
var encodedValue = encodable.EncodeString();
yield return new StringEncodedConstantDirective(constantName, qualifiedTypeName, encodedValue);
}
else if (constantValue is Layout.ILayout constantLayout && Layout.PredefinedLayouts.TryConvertToString(constantLayout, out var constantLayoutString))
{
qualifiedTypeName = GetQualifiedName(UIXTypes.MapIDToType(UIXTypeID.Layout));
yield return new StringEncodedConstantDirective(constantName, qualifiedTypeName, constantLayoutString);
}
else if (typeSchema.SupportsTypeConversion(stringTypeSchema))
{
var encodedValue = constantValue.ToString();
yield return new StringEncodedConstantDirective(constantName, qualifiedTypeName, encodedValue);
}
else if (typeSchema.SupportsBinaryEncoding)
{
ByteCodeWriter writer = new();
typeSchema.EncodeBinary(writer, constantValue);
var reader = writer.CreateReader();
byte[] encodedBytes = new byte[reader.Size];
Marshal.Copy(reader.GetAddress(0), encodedBytes, 0, (int)reader.Size);
yield return new BinaryEncodedConstantDirective(constantName, qualifiedTypeName, encodedBytes);
}
else
{
XName constElemName = qualifiedTypeName.NamespacePrefix is null
? qualifiedTypeName.TypeName
: XName.Get(qualifiedTypeName.TypeName, qualifiedTypeName.NamespacePrefix);
XElement constElem = new(constElemName);
var defaultConstantValue = typeSchema.ConstructDefault();
foreach (var prop in typeSchema.Properties)
{
// No need to serialize properties that can't be set
if (!prop.CanWrite)
continue;
var defaultPropValue = prop.GetValue(defaultConstantValue);
var propValue = prop.GetValue(constantValue);
var encodedPropValue = EncodeSimpleConstant(propValue);
var encodedDefaultPropValue = EncodeSimpleConstant(defaultPropValue);
// No need to serialize properties that are at their default value
if (encodedPropValue == encodedDefaultPropValue)
continue;
constElem.SetAttributeValue(prop.Name, encodedPropValue);
}
var constructor = constElem.ToString(SaveOptions.DisableFormatting);
yield return new ConstantDirective(constantName, qualifiedTypeName, constructor);
}
} }
} }
@@ -376,6 +317,78 @@ public class Disassembler
labels.Add(new(labelName)); labels.Add(new(labelName));
} }
private ConstantDirective EncodeConstant(object constantValue, string constantName, TypeSchema typeSchema)
{
var qualifiedTypeName = GetQualifiedName(typeSchema);
// String encodable
if (constantValue is IStringEncodable encodable)
{
var encodedValue = encodable.EncodeString();
return new StringEncodedConstantDirective(constantName, qualifiedTypeName, encodedValue);
}
// Custom handling for ILayout
if (constantValue is Layout.ILayout constantLayout && Layout.PredefinedLayouts.TryConvertToString(constantLayout, out var constantLayoutString))
{
qualifiedTypeName = GetQualifiedName(UIXTypes.MapIDToType(UIXTypeID.Layout));
return new CanonicalInstanceConstantDirective(constantName, qualifiedTypeName, constantLayoutString);
}
if (typeSchema.SupportsTypeConversion(_stringTypeSchema))
{
var encodedValue = constantValue.ToString();
return new StringEncodedConstantDirective(constantName, qualifiedTypeName, encodedValue);
}
// Binary encodable
if (typeSchema.SupportsBinaryEncoding)
{
ByteCodeWriter writer = new();
typeSchema.EncodeBinary(writer, constantValue);
var reader = writer.CreateReader();
byte[] encodedBytes = new byte[reader.Size];
Marshal.Copy(reader.GetAddress(0), encodedBytes, 0, (int)reader.Size);
reader.Dispose(null);
return new BinaryEncodedConstantDirective(constantName, qualifiedTypeName, encodedBytes);
}
#if DEBUG
throw new NotSupportedException($"Unable to encode constant value '{constantValue}' of type '{qualifiedTypeName}'");
#else
XName constElemName = qualifiedTypeName.NamespacePrefix is null
? qualifiedTypeName.TypeName
: XName.Get(qualifiedTypeName.TypeName, qualifiedTypeName.NamespacePrefix);
XElement constElem = new(constElemName);
var defaultConstantValue = typeSchema.ConstructDefault();
foreach (var prop in typeSchema.Properties)
{
// No need to serialize properties that can't be set
if (!prop.CanWrite)
continue;
var defaultPropValue = prop.GetValue(defaultConstantValue);
var propValue = prop.GetValue(constantValue);
var encodedPropValue = EncodeSimpleConstant(propValue);
var encodedDefaultPropValue = EncodeSimpleConstant(defaultPropValue);
// No need to serialize properties that are at their default value
if (encodedPropValue == encodedDefaultPropValue)
continue;
constElem.SetAttributeValue(prop.Name, encodedPropValue);
}
var constructor = constElem.ToString(SaveOptions.DisableFormatting);
return new ConstantDirective(constantName, qualifiedTypeName, constructor);
#endif
}
private static string EncodeSimpleConstant(object value) private static string EncodeSimpleConstant(object value)
{ {
return value is IStringEncodable encodable return value is IStringEncodable encodable
+31 -5
View File
@@ -87,9 +87,23 @@ partial class Lexer
if (!typeNameResult.WasSuccessful) if (!typeNameResult.WasSuccessful)
return Result.Failure<IDirective>(input, "Invalid constant directive", ["Expected qualified name of type to construct"]); return Result.Failure<IDirective>(input, "Invalid constant directive", ["Expected qualified name of type to construct"]);
var binaryEncodingMarkerResult = Parse.String(".bin")(input); var encodingMarkerResult = Parse.Char('.')(input);
input = binaryEncodingMarkerResult.Remainder; input = encodingMarkerResult.Remainder;
var binaryEncoded = binaryEncodingMarkerResult.WasSuccessful; if (!encodingMarkerResult.WasSuccessful)
return Result.Failure<IDirective>(input, "Invalid constant directive", ["Expected '.', followed by the persist mode."]);
var persistModeResult = Parse.CharExcept('(').AtLeastOnce().Text().Token()(input);
input = persistModeResult.Remainder;
if (!persistModeResult.WasSuccessful)
return Result.Failure<IDirective>(input, "Invalid constant directive", ["No persist mode was specified."]);
Markup.MarkupConstantPersistMode? persistMode = persistModeResult.Value.ToLowerInvariant() switch
{
"bin" => Markup.MarkupConstantPersistMode.Binary,
"str" => Markup.MarkupConstantPersistMode.FromString,
"can" => Markup.MarkupConstantPersistMode.Canonical,
_ => null
};
var openBracketResult = Parse.Char('(').Token()(input); var openBracketResult = Parse.Char('(').Token()(input);
input = openBracketResult.Remainder; input = openBracketResult.Remainder;
@@ -109,7 +123,7 @@ partial class Lexer
var constantName = constNameResult.Value; var constantName = constNameResult.Value;
var typeName = typeNameResult.Value; var typeName = typeNameResult.Value;
if (!binaryEncoded) if (persistMode == Markup.MarkupConstantPersistMode.FromString)
{ {
directive = new StringEncodedConstantDirective(constantName, typeName, contentResult.Value) directive = new StringEncodedConstantDirective(constantName, typeName, contentResult.Value)
{ {
@@ -117,7 +131,15 @@ partial class Lexer
Column = column, Column = column,
}; };
} }
else else if (persistMode == Markup.MarkupConstantPersistMode.Canonical)
{
directive = new CanonicalInstanceConstantDirective(constantName, typeName, contentResult.Value)
{
Line = line,
Column = column,
};
}
else if (persistMode == Markup.MarkupConstantPersistMode.Binary)
{ {
byte[] constantBytes; byte[] constantBytes;
var byteParts = contentResult.Value.Split(','); var byteParts = contentResult.Value.Split(',');
@@ -156,6 +178,10 @@ partial class Lexer
Column = column, Column = column,
}; };
} }
else
{
return Result.Failure<IDirective>(input, "Invalid constant directive", [$"'{persistModeResult.Value}' is not a valid persist mode."]);
}
} }
break; break;
+79
View File
@@ -0,0 +1,79 @@
using Microsoft.Iris.Markup;
using System;
using System.Linq;
namespace Microsoft.Iris.Asm.Models;
public record ConstantDirective : Directive
{
public ConstantDirective(string name, QualifiedTypeName typeName, string constructor) : base("constant")
{
Name = name;
TypeName = typeName;
Constructor = constructor;
}
public string Name { get; }
public QualifiedTypeName TypeName { get; }
public string Constructor { get; }
public MarkupConstantPersistMode PersistMode { get; init; }
public override string ToString() => $"{base.ToString()} {Name} = {Constructor}";
}
public record StringEncodedConstantDirective : ConstantDirective
{
public StringEncodedConstantDirective(string name, QualifiedTypeName typeName, string content)
: base(name, typeName, $"{typeName}.str({content})")
{
Content = content;
PersistMode = MarkupConstantPersistMode.FromString;
}
public string Content { get; }
public override string ToString() => base.ToString();
}
public record BinaryEncodedConstantDirective : ConstantDirective
{
public BinaryEncodedConstantDirective(string name, QualifiedTypeName typeName, byte[] content)
: base(name, typeName, GetConstructor(typeName, content))
{
Content = content;
PersistMode = MarkupConstantPersistMode.Binary;
}
public byte[] Content { get; }
public override string ToString() => base.ToString();
private static string GetConstructor(QualifiedTypeName typeName, byte[] content)
{
string contentStr;
if (content.Length == 1)
contentStr = $"0x{content[0]:X2}";
else if (content.Length == 2)
contentStr = $"0x{BitConverter.ToUInt16(content, 0):X4}";
else if (content.Length == 4)
contentStr = $"0x{BitConverter.ToUInt32(content, 0):X8}";
else
contentStr = string.Join(", ", content.Select(b => $"0x{b:X2}"));
return $"{typeName}.bin({contentStr})";
}
}
public record CanonicalInstanceConstantDirective : ConstantDirective
{
public CanonicalInstanceConstantDirective(string name, QualifiedTypeName typeName, string canonicalName)
: base(name, typeName, $"{typeName}.can({canonicalName})")
{
CanonicalName = canonicalName;
PersistMode = MarkupConstantPersistMode.Canonical;
}
public string CanonicalName { get; }
public override string ToString() => base.ToString();
}
-57
View File
@@ -15,63 +15,6 @@ public record SectionDirective : Directive
public override string ToString() => $"{base.ToString()} {Name}"; public override string ToString() => $"{base.ToString()} {Name}";
} }
public record ConstantDirective : Directive
{
public ConstantDirective(string name, QualifiedTypeName typeName, string constructor) : base("constant")
{
Name = name;
TypeName = typeName;
Constructor = constructor;
}
public string Name { get; }
public QualifiedTypeName TypeName { get; }
public string Constructor { get; }
public override string ToString() => $"{base.ToString()} {Name} = {Constructor}";
}
public record StringEncodedConstantDirective : ConstantDirective
{
public StringEncodedConstantDirective(string name, QualifiedTypeName typeName, string content)
: base(name, typeName, $"{typeName}({content})")
{
Content = content;
}
public string Content { get; }
public override string ToString() => base.ToString();
}
public record BinaryEncodedConstantDirective : ConstantDirective
{
public BinaryEncodedConstantDirective(string name, QualifiedTypeName typeName, byte[] content)
: base(name, typeName, GetConstructor(typeName, content))
{
Content = content;
}
public byte[] Content { get; }
public override string ToString() => base.ToString();
private static string GetConstructor(QualifiedTypeName typeName, byte[] content)
{
string contentStr;
if (content.Length == 1)
contentStr = $"0x{content[0]:X2}";
else if (content.Length == 2)
contentStr = $"0x{BitConverter.ToUInt16(content, 0):X4}";
else if (content.Length == 4)
contentStr = $"0x{BitConverter.ToUInt32(content, 0):X8}";
else
contentStr = string.Join(", ", content.Select(b => $"0x{b:X2}"));
return $"{typeName}.bin({contentStr})";
}
}
public record ExportDirective : Directive public record ExportDirective : Directive
{ {
public ExportDirective(string labelPrefix, uint listenerCount, string baseTypeName) : base("export") public ExportDirective(string labelPrefix, uint listenerCount, string baseTypeName) : base("export")
+6 -6
View File
@@ -32,12 +32,12 @@ public class Assembly(ITestOutputHelper output)
.import-mbrs UI{Locals, Content} .import-mbrs UI{Locals, Content}
.import-mbrs Text{Color, Content, Font} .import-mbrs Text{Color, Content, Font}
.constant const0 = Color(255, 255, 0, 0) .constant const0 = Color.str(255, 255, 0, 0)
.constant const1 = String(Howdy from Microsoft.Iris!) .constant const1 = String.str(Howdy from Microsoft.Iris!)
.constant const2 = String(SelectCommand) .constant const2 = String.str(SelectCommand)
.constant const3 = Color(255, 0, 0, 255) .constant const3 = Color.str(255, 0, 0, 255)
.constant const4 = Font(JetBrains Mono) .constant const4 = Font.str(JetBrains Mono)
.constant const5 = String(This is some blue text) .constant const5 = String.str(This is some blue text)
.section object .section object
Default_cont: Default_cont: