Skip to content

Commit

Permalink
lingo: generate shared instruction reading stub
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulusParssinen committed Feb 23, 2024
1 parent 221845c commit d74edb4
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 96 deletions.
183 changes: 117 additions & 66 deletions Shockky.SourceGeneration.Tests/InstructionGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,50 +9,122 @@ namespace Shockky.SourceGeneration.Tests;

public sealed class InstructionGeneratorTests
{
[Fact]
public void OpWithoutImmediate()
{
string source = """
namespace Shockky.Lingo.Instructions;

public enum OPCode : byte
public const string ExpectedReturnOutput = """
namespace Shockky.Lingo.Instructions;

[global::System.CodeDom.Compiler.GeneratedCode("InstructionGenerator", <ASSEMBLY_VERSION>)]
[global::System.Diagnostics.DebuggerNonUserCode]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public sealed class Return : IInstruction
{
public static readonly Return Default = new();

/// <inheritdoc />
public OPCode OP => OPCode.Return;

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
[global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)]
public int Immediate { get; set; }

public int GetSize() => sizeof(OPCode);

public void WriteTo(global::Shockky.IO.ShockwaveWriter output)
{
[OP] Return = 0x01,
output.Write((byte)OP);
}
""";
string expectedSyntaxForReturn = """
namespace Shockky.Lingo.Instructions;

}
""";
public const string ExpectedPushIntOutput = """
namespace Shockky.Lingo.Instructions;

[global::System.CodeDom.Compiler.GeneratedCode("InstructionGenerator", <ASSEMBLY_VERSION>)]
[global::System.Diagnostics.DebuggerNonUserCode]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public sealed class PushInt : IInstruction
{
/// <inheritdoc />
public OPCode OP => OPCode.PushInt;

public int Immediate { get; set; }

public int GetSize()
{
uint imm = (uint)Immediate;
if (imm <= byte.MaxValue) return sizeof(OPCode) + 1;
else if (imm <= ushort.MaxValue) return sizeof(OPCode) + 2;
else return sizeof(OPCode) + 4;
}

public void WriteTo(global::Shockky.IO.ShockwaveWriter output)
{
byte op = (byte)OP;

if (Immediate <= byte.MaxValue)
{
output.Write(op);
output.Write((byte)Immediate);
}
else if (Immediate <= ushort.MaxValue)
{
output.Write(op + 0x40);
output.Write((ushort)Immediate);
}
else
{
output.Write(op + 0x80);
output.Write(Immediate);
}
}
}
""";
public const string ExpectedSharedReadMethod = """
namespace Shockky.Lingo.Instructions;

public partial interface IInstruction
{
[global::System.CodeDom.Compiler.GeneratedCode("InstructionGenerator", <ASSEMBLY_VERSION>)]
[global::System.Diagnostics.DebuggerNonUserCode]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public sealed class Return : IInstruction
public static IInstruction Read(ref global::Shockky.IO.ShockwaveReader input)
{
public static readonly Return Default = new();

/// <inheritdoc />
public OPCode OP => OPCode.Return;
byte op = input.ReadByte();
int immediate = op >> 6 switch
{
1 => input.ReadByte(),
2 => input.ReadInt16(),
3 => input.ReadInt32(),
_ => 0
};

[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
[global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)]
public int Immediate { get; set; }
return (OPCode)op switch
{
OPCode.Return => new Return(),
OPCode.PushInt => new PushInt(),
_ => throw null
};
}
}
""";

public int GetSize() => sizeof(OPCode);
[Fact]
public void InstructionGenerator_Generates_OpWithoutImmediate()
{
string source = """
namespace Shockky.Lingo.Instructions;

public void WriteTo(global::Shockky.IO.ShockwaveWriter output)
{
output.Write((byte)OP);
}
public enum OPCode : byte
{
[OP] Return = 0x01,
}
""";

VerifyGenerateSources(source,
[new InstructionGenerator()],
("Shockky.Lingo.Instructions.Return.g.cs", expectedSyntaxForReturn));
("Shockky.Lingo.Instructions.Return.g.cs", ExpectedReturnOutput));
}

[Fact]
public void OpWithImmediate()
public void InstructionGenerator_Generates_OpWithImmediate()
{
string source = """
namespace Shockky.Lingo.Instructions;
Expand All @@ -62,53 +134,32 @@ public enum OPCode : byte
[OP(ImmediateKind.Integer)] PushInt = 0x41,
}
""";
string expected = """
namespace Shockky.Lingo.Instructions;

[global::System.CodeDom.Compiler.GeneratedCode("InstructionGenerator", <ASSEMBLY_VERSION>)]
[global::System.Diagnostics.DebuggerNonUserCode]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public sealed class PushInt : IInstruction
{
/// <inheritdoc />
public OPCode OP => OPCode.PushInt;

public int Immediate { get; set; }
VerifyGenerateSources(source,
[new InstructionGenerator()],
("Shockky.Lingo.Instructions.PushInt.g.cs", ExpectedPushIntOutput));
}

public int GetSize()
{
uint imm = (uint)Immediate;
if (imm <= byte.MaxValue) return sizeof(OPCode) + 1;
else if (imm <= ushort.MaxValue) return sizeof(OPCode) + 2;
else return sizeof(OPCode) + 4;
}
[Fact]
public void InstructionGenerator_Generates_SharedReadLogic()
{
string source = """
namespace Shockky.Lingo.Instructions;

public void WriteTo(global::Shockky.IO.ShockwaveWriter output)
{
byte op = (byte)OP;

if (Immediate <= byte.MaxValue)
{
output.Write(op);
output.Write((byte)Immediate);
}
else if (Immediate <= ushort.MaxValue)
{
output.Write(op + 0x40);
output.Write((ushort)Immediate);
}
else
{
output.Write(op + 0x80);
output.Write(Immediate);
}
}
public enum OPCode : byte
{
[OP] Return = 0x01,
[OP(ImmediateKind.Integer)] PushInt = 0x41,
}
""";

VerifyGenerateSources(source,
[new InstructionGenerator()],
("Shockky.Lingo.Instructions.PushInt.g.cs", expected));
[
("Shockky.Lingo.Instructions.Return.g.cs", ExpectedReturnOutput),
("Shockky.Lingo.Instructions.PushInt.g.cs", ExpectedPushIntOutput),
("Shockky.Lingo.Instructions.IInstruction.Read.g.cs", ExpectedSharedReadMethod)
]);
}

/// <summary>
Expand Down
29 changes: 17 additions & 12 deletions Shockky.SourceGeneration/Helpers/IndentedTextWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,16 @@ public void DecreaseIndent()
/// Writes a block to the underlying buffer.
/// </summary>
/// <returns>A <see cref="Block"/> value to close the open block with.</returns>
public Block WriteBlock()
public Block WriteBlock(string? clause = default, bool isExpression = false)
{
if (clause is not null)
{
WriteLine(clause);
}
WriteLine("{");
IncreaseIndent();

return new(this);
return new(this, isExpression);
}

/// <summary>
Expand Down Expand Up @@ -352,25 +356,26 @@ private void WriteRawText(ReadOnlySpan<char> content)
/// <summary>
/// Represents an indented block that needs to be closed.
/// </summary>
/// <param name="writer">The input <see cref="IndentedTextWriter"/> instance to wrap.</param>
public struct Block(IndentedTextWriter writer) : IDisposable
/// <param name="_writer">The input <see cref="IndentedTextWriter"/> instance to wrap.</param>
public readonly struct Block(IndentedTextWriter _writer, bool isExpression = false) : IDisposable
{
/// <summary>
/// The <see cref="IndentedTextWriter"/> instance to write to.
/// </summary>
private IndentedTextWriter? writer = writer;
private readonly IndentedTextWriter? _writer = _writer;

/// <summary>
/// Indicates whether the indented block is an expression and requires a semicolon to close the block.
/// </summary>
private readonly bool _isExpression = isExpression;

/// <inheritdoc/>
public void Dispose()
{
IndentedTextWriter? writer = this.writer;

this.writer = null;

if (writer is not null)
if (_writer is not null)
{
writer.DecreaseIndent();
writer.WriteLine("}");
_writer.DecreaseIndent();
_writer.WriteLine(_isExpression ? "};" : "}");
}
}
}
Expand Down
Loading

0 comments on commit d74edb4

Please sign in to comment.