From 6cfa7f87008bb6d779cad5161e88aae154cb89ac Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Sat, 3 Aug 2024 09:59:47 -0500 Subject: [PATCH] Add a bunch of methods. --- .../Writing/ElementValueEncoderTests.cs | 156 ++++++++++++++++++ .../Writing/TypeAnnotationEncoderTests.cs | 55 ++++++ .../Writing/TypePathEncoderTests.cs | 135 +++++++++++++++ .../Writing/AnnotationEncoder.cs | 25 ++- .../Writing/AnnotationTableEncoder.cs | 49 +++++- .../Writing/BootstrapArgumentsTableEncoder.cs | 29 +++- .../Writing/BootstrapMethodsTableEncoder.cs | 12 +- .../Writing/ClassConstantTableEncoder.cs | 27 +++ .../Writing/ElementValueEncoder.cs | 7 +- .../Writing/ElementValuePairTableEncoder.cs | 155 ++++++++++++++++- .../Writing/ElementValueTableEncoder.cs | 146 ++++++++++++++-- .../Writing/ExceptionTableEncoder.cs | 37 +++++ .../Writing/InnerClassesTableEncoder.cs | 39 ++++- .../Writing/LineNumberTableEncoder.cs | 37 +++++ .../Writing/LocalVariableTableEncoder.cs | 37 +++++ .../LocalVariableTargetTableEncoder.cs | 33 +++- .../Writing/TypeAnnotationEncoder.cs | 20 +-- .../Writing/TypeAnnotationTableEncoder.cs | 7 +- src/IKVM.ByteCode/Writing/TypePathEncoder.cs | 39 +++-- 19 files changed, 979 insertions(+), 66 deletions(-) create mode 100644 src/IKVM.ByteCode.Tests/Writing/ElementValueEncoderTests.cs create mode 100644 src/IKVM.ByteCode.Tests/Writing/TypeAnnotationEncoderTests.cs create mode 100644 src/IKVM.ByteCode.Tests/Writing/TypePathEncoderTests.cs diff --git a/src/IKVM.ByteCode.Tests/Writing/ElementValueEncoderTests.cs b/src/IKVM.ByteCode.Tests/Writing/ElementValueEncoderTests.cs new file mode 100644 index 0000000..33e42e8 --- /dev/null +++ b/src/IKVM.ByteCode.Tests/Writing/ElementValueEncoderTests.cs @@ -0,0 +1,156 @@ +using System; + +using FluentAssertions; + +using IKVM.ByteCode.Buffers; +using IKVM.ByteCode.Parsing; +using IKVM.ByteCode.Writing; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.ByteCode.Tests.Writing +{ + + [TestClass] + public class ElementValueEncoderTests + { + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldThrowOnEncodeTwo() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.Byte(new IntegerConstantHandle(1)); + encoder.Byte(new IntegerConstantHandle(1)); + } + + [TestMethod] + public void CanEncodeByte() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.Byte(new IntegerConstantHandle(1)); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var tag).Should().BeTrue(); + tag.Should().Be((byte)ElementValueTag.Byte); + r.TryReadU2(out var constValueIndex).Should().BeTrue(); + constValueIndex.Should().Be(1); + } + + [TestMethod] + public void CanEncodeChar() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.Char(new IntegerConstantHandle(1)); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var tag).Should().BeTrue(); + tag.Should().Be((byte)ElementValueTag.Char); + r.TryReadU2(out var constValueIndex).Should().BeTrue(); + constValueIndex.Should().Be(1); + } + + [TestMethod] + public void CanEncodeDouble() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.Double(new DoubleConstantHandle(1)); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var tag).Should().BeTrue(); + tag.Should().Be((byte)ElementValueTag.Double); + r.TryReadU2(out var constValueIndex).Should().BeTrue(); + constValueIndex.Should().Be(1); + } + + [TestMethod] + public void CanEncodeFloat() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.Float(new FloatConstantHandle(1)); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var tag).Should().BeTrue(); + tag.Should().Be((byte)ElementValueTag.Float); + r.TryReadU2(out var constValueIndex).Should().BeTrue(); + constValueIndex.Should().Be(1); + } + + [TestMethod] + public void CanEncodeInteger() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.Integer(new IntegerConstantHandle(1)); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var tag).Should().BeTrue(); + tag.Should().Be((byte)ElementValueTag.Integer); + r.TryReadU2(out var constValueIndex).Should().BeTrue(); + constValueIndex.Should().Be(1); + } + + [TestMethod] + public void CanEncodeLong() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.Long(new LongConstantHandle(1)); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var tag).Should().BeTrue(); + tag.Should().Be((byte)ElementValueTag.Long); + r.TryReadU2(out var constValueIndex).Should().BeTrue(); + constValueIndex.Should().Be(1); + } + + [TestMethod] + public void CanEncodeShort() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.Short(new IntegerConstantHandle(1)); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var tag).Should().BeTrue(); + tag.Should().Be((byte)ElementValueTag.Short); + r.TryReadU2(out var constValueIndex).Should().BeTrue(); + constValueIndex.Should().Be(1); + } + + [TestMethod] + public void CanEncodeBoolean() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.Boolean(new IntegerConstantHandle(1)); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var tag).Should().BeTrue(); + tag.Should().Be((byte)ElementValueTag.Boolean); + r.TryReadU2(out var constValueIndex).Should().BeTrue(); + constValueIndex.Should().Be(1); + } + + [TestMethod] + public void CanEncodeString() + { + var builder = new BlobBuilder(); + var encoder = new ElementValueEncoder(builder); + encoder.String(new Utf8ConstantHandle(1)); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var tag).Should().BeTrue(); + tag.Should().Be((byte)ElementValueTag.String); + r.TryReadU2(out var constValueIndex).Should().BeTrue(); + constValueIndex.Should().Be(1); + } + + } + +} diff --git a/src/IKVM.ByteCode.Tests/Writing/TypeAnnotationEncoderTests.cs b/src/IKVM.ByteCode.Tests/Writing/TypeAnnotationEncoderTests.cs new file mode 100644 index 0000000..4a2d39c --- /dev/null +++ b/src/IKVM.ByteCode.Tests/Writing/TypeAnnotationEncoderTests.cs @@ -0,0 +1,55 @@ +using FluentAssertions; + +using IKVM.ByteCode.Buffers; +using IKVM.ByteCode.Parsing; +using IKVM.ByteCode.Writing; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.ByteCode.Tests.Writing +{ + + [TestClass] + public class TypeAnnotationEncoderTests + { + + [TestMethod] + public void CanEncodeClassTypeParameter() + { + var builder = new BlobBuilder(); + new TypeAnnotationEncoder(builder) + .ClassTypeParameter( + 0, + path => path.Array(), + new Utf8ConstantHandle(1), + elements => elements + .Element( + new Utf8ConstantHandle(2), + value => value.Integer(new IntegerConstantHandle(3)))); + + var r = new ClassFormatReader(builder.ToArray()); + r.TryReadU1(out var targetType).Should().BeTrue(); + targetType.Should().Be((byte)TypeAnnotationTargetType.ClassTypeParameter); + r.TryReadU1(out var typeParameterIndex).Should().BeTrue(); + typeParameterIndex.Should().Be(0); + r.TryReadU1(out var typePathCount).Should().BeTrue(); + typePathCount.Should().Be(1); + r.TryReadU1(out var typePathKind).Should().BeTrue(); + typePathKind.Should().Be((byte)TypePathKind.Array); + r.TryReadU1(out var typePathArgumentIndex).Should().BeTrue(); + typePathArgumentIndex.Should().Be(0); + r.TryReadU2(out var typeIndex).Should().BeTrue(); + typeIndex.Should().Be(1); + r.TryReadU2(out var elementValueCount).Should().BeTrue(); + elementValueCount.Should().Be(1); + r.TryReadU2(out var elementNameIndex).Should().BeTrue(); + elementNameIndex.Should().Be(2); + r.TryReadU1(out var elementTag).Should().BeTrue(); + elementTag.Should().Be((byte)'I'); + r.TryReadU2(out var elementValue).Should().BeTrue(); + elementValue.Should().Be(3); + } + + } + +} diff --git a/src/IKVM.ByteCode.Tests/Writing/TypePathEncoderTests.cs b/src/IKVM.ByteCode.Tests/Writing/TypePathEncoderTests.cs new file mode 100644 index 0000000..e898927 --- /dev/null +++ b/src/IKVM.ByteCode.Tests/Writing/TypePathEncoderTests.cs @@ -0,0 +1,135 @@ +using FluentAssertions; + +using IKVM.ByteCode.Buffers; +using IKVM.ByteCode.Parsing; +using IKVM.ByteCode.Writing; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace IKVM.ByteCode.Tests.Writing +{ + + [TestClass] + public class TypePathEncoderTests + { + + [TestMethod] + public void CanEncodeArray() + { + var builder = new BlobBuilder(); + new TypePathEncoder(builder).Array(); + + var w = new ClassFormatReader(builder.ToArray()); + w.TryReadU1(out var l).Should().BeTrue(); + l.Should().Be(1); + w.TryReadU1(out var k).Should().BeTrue(); + k.Should().Be(0); + w.TryReadU1(out var a).Should().BeTrue(); + a.Should().Be(0); + } + + [TestMethod] + public void CanEncodeInnerType() + { + var builder = new BlobBuilder(); + new TypePathEncoder(builder).InnerType(); + + var w = new ClassFormatReader(builder.ToArray()); + w.TryReadU1(out var l).Should().BeTrue(); + l.Should().Be(1); + w.TryReadU1(out var k).Should().BeTrue(); + k.Should().Be(1); + w.TryReadU1(out var a).Should().BeTrue(); + a.Should().Be(0); + } + + [TestMethod] + public void CanEncodeWildcard() + { + var builder = new BlobBuilder(); + new TypePathEncoder(builder).Wildcard(); + + var w = new ClassFormatReader(builder.ToArray()); + w.TryReadU1(out var l).Should().BeTrue(); + l.Should().Be(1); + w.TryReadU1(out var k).Should().BeTrue(); + k.Should().Be(2); + w.TryReadU1(out var a).Should().BeTrue(); + a.Should().Be(0); + } + + [TestMethod] + public void CanEncodeTypeArgument() + { + var builder = new BlobBuilder(); + new TypePathEncoder(builder).TypeArgument(1); + + var w = new ClassFormatReader(builder.ToArray()); + w.TryReadU1(out var l).Should().BeTrue(); + l.Should().Be(1); + w.TryReadU1(out var k).Should().BeTrue(); + k.Should().Be(3); + w.TryReadU1(out var a).Should().BeTrue(); + a.Should().Be(1); + } + + [TestMethod] + public void CanEncodeMultiple() + { + var builder = new BlobBuilder(); + new TypePathEncoder(builder).Array().Array(); + + var w = new ClassFormatReader(builder.ToArray()); + w.TryReadU1(out var l).Should().BeTrue(); + l.Should().Be(2); + w.TryReadU1(out var k1).Should().BeTrue(); + k1.Should().Be(0); + w.TryReadU1(out var a1).Should().BeTrue(); + a1.Should().Be(0); + w.TryReadU1(out var k2).Should().BeTrue(); + k2.Should().Be(0); + w.TryReadU1(out var a2).Should().BeTrue(); + a2.Should().Be(0); + } + + [TestMethod] + public void CanEncodeRecord() + { + var builder = new BlobBuilder(); + new TypePathEncoder(builder).Encode(new TypePathRecord([ + new TypePathItemRecord(TypePathKind.Array, 0), + new TypePathItemRecord(TypePathKind.Array, 0), + ])); + + var w = new ClassFormatReader(builder.ToArray()); + w.TryReadU1(out var l).Should().BeTrue(); + l.Should().Be(2); + w.TryReadU1(out var k1).Should().BeTrue(); + k1.Should().Be(0); + w.TryReadU1(out var a1).Should().BeTrue(); + a1.Should().Be(0); + w.TryReadU1(out var k2).Should().BeTrue(); + k2.Should().Be(0); + w.TryReadU1(out var a2).Should().BeTrue(); + a2.Should().Be(0); + } + + [TestMethod] + public void CanEncodeRecordAndParse() + { + var builder = new BlobBuilder(); + new TypePathEncoder(builder).Encode(new TypePathRecord([ + new TypePathItemRecord(TypePathKind.Array, 0), + new TypePathItemRecord(TypePathKind.Array, 0), + ])); + + var r = new ClassFormatReader(builder.ToArray()); + TypePathRecord.TryRead(ref r, out var record).Should().BeTrue(); + record.Path.Should().HaveCount(2); + record.Path[0].Kind.Should().Be(TypePathKind.Array); + record.Path[1].Kind.Should().Be(TypePathKind.Array); + } + + } + +} diff --git a/src/IKVM.ByteCode/Writing/AnnotationEncoder.cs b/src/IKVM.ByteCode/Writing/AnnotationEncoder.cs index af5201c..2aaf33a 100644 --- a/src/IKVM.ByteCode/Writing/AnnotationEncoder.cs +++ b/src/IKVM.ByteCode/Writing/AnnotationEncoder.cs @@ -6,10 +6,25 @@ namespace IKVM.ByteCode.Writing { + /// + /// Encodes an annnotation structure: + /// + /// + /// annotation { + /// u2 type_index; + /// u2 num_element_value_pairs; + /// { + /// u2 element_name_index; + /// element_value value; + /// } + /// element_value_pairs[num_element_value_pairs]; + /// } + /// public struct AnnotationEncoder { readonly BlobBuilder _builder; + byte _count; /// /// Initializes a new instance. @@ -27,20 +42,26 @@ public AnnotationEncoder(BlobBuilder builder) /// public void Encode(AnnotationRecord annotation) { - Encode(annotation.Type, e => e.Encode(annotation.Elements)); + Encode(annotation.Type, e => e.AddMany(annotation.Elements)); } /// - /// Encodes a new element_value_pair. + /// Encodes a new annotation. /// + /// /// /// public void Encode(Utf8ConstantHandle type, Action elementValuePairs) { + if (_count > 0) + throw new InvalidOperationException("Only a single annotation can be encoded by this encoder."); + var w = new ClassFormatWriter(_builder.ReserveBytes(ClassFormatWriter.U2).GetBytes()); w.TryWriteU2(type.Index); elementValuePairs(new ElementValuePairTableEncoder(_builder)); + _count++; } + } } diff --git a/src/IKVM.ByteCode/Writing/AnnotationTableEncoder.cs b/src/IKVM.ByteCode/Writing/AnnotationTableEncoder.cs index 7f93dcb..e4fdd54 100644 --- a/src/IKVM.ByteCode/Writing/AnnotationTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/AnnotationTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -31,13 +32,59 @@ public AnnotationTableEncoder(BlobBuilder builder) /// Adds an annotation. /// /// - public AnnotationTableEncoder Annotation(Action annotation) + public AnnotationTableEncoder Add(Action annotation) { annotation(new AnnotationEncoder(_builder)); new ClassFormatWriter(_countBlob.GetBytes()).TryWriteU2(++_count); return this; } + /// + /// Encodes an existing annotation. + /// + /// + public AnnotationTableEncoder Add(AnnotationRecord annotation) + { + return Add(e => e.Encode(annotation)); + } + + /// + /// Encodes a new annotation. + /// + /// + /// + /// + public AnnotationTableEncoder Add(Utf8ConstantHandle type, Action elementValuePairs) + { + return Add(e => e.Encode(type, elementValuePairs)); + } + + /// + /// Adds multiple existing annotations. + /// + /// + /// + public AnnotationTableEncoder AddMany(ReadOnlySpan annotations) + { + foreach (var i in annotations) + Add(i); + + return this; + } + + /// + /// Adds multiple existing annotations. + /// + /// + /// + public AnnotationTableEncoder AddMany(IEnumerable annotations) + { + foreach (var i in annotations) + Add(i); + + return this; + } + } } diff --git a/src/IKVM.ByteCode/Writing/BootstrapArgumentsTableEncoder.cs b/src/IKVM.ByteCode/Writing/BootstrapArgumentsTableEncoder.cs index ebfc7d1..63ff939 100644 --- a/src/IKVM.ByteCode/Writing/BootstrapArgumentsTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/BootstrapArgumentsTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -32,7 +33,7 @@ public BootstrapArgumentsTableEncoder(BlobBuilder builder) /// /// /// - public BootstrapArgumentsTableEncoder Argument(ConstantHandle argument) + public BootstrapArgumentsTableEncoder Add(ConstantHandle argument) { var w = new ClassFormatWriter(_builder.ReserveBytes(ClassFormatWriter.U2).GetBytes()); w.TryWriteU2(argument.Index); @@ -40,6 +41,32 @@ public BootstrapArgumentsTableEncoder Argument(ConstantHandle argument) return this; } + /// + /// Adds multiple bootstrap method arguments. + /// + /// + /// + public BootstrapArgumentsTableEncoder Add(ReadOnlySpan arguments) + { + foreach (var i in arguments) + Add(i); + + return this; + } + + /// + /// Adds multiple bootstrap method arguments. + /// + /// + /// + public BootstrapArgumentsTableEncoder Add(IEnumerable arguments) + { + foreach (var i in arguments) + Add(i); + + return this; + } + } } diff --git a/src/IKVM.ByteCode/Writing/BootstrapMethodsTableEncoder.cs b/src/IKVM.ByteCode/Writing/BootstrapMethodsTableEncoder.cs index 8752c42..8b3d0fa 100644 --- a/src/IKVM.ByteCode/Writing/BootstrapMethodsTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/BootstrapMethodsTableEncoder.cs @@ -33,7 +33,7 @@ public BootstrapMethodsTableEncoder(BlobBuilder builder) /// /// /// - public BootstrapMethodsTableEncoder BootstrapMethod(MethodHandleConstantHandle methodRef, Action arguments) + public BootstrapMethodsTableEncoder Add(MethodHandleConstantHandle methodRef, Action arguments) { var w = new ClassFormatWriter(_builder.ReserveBytes(ClassFormatWriter.U2).GetBytes()); w.TryWriteU2(methodRef.Index); @@ -42,6 +42,16 @@ public BootstrapMethodsTableEncoder BootstrapMethod(MethodHandleConstantHandle m return this; } + /// + /// Adds an existing bootstrap method. + /// + /// + /// + public BootstrapMethodsTableEncoder Add(BootstrapMethodsAttributeMethodRecord record) + { + return Add(record.Method, e => e.Add(record.Arguments.AsSpan())); + } + } } diff --git a/src/IKVM.ByteCode/Writing/ClassConstantTableEncoder.cs b/src/IKVM.ByteCode/Writing/ClassConstantTableEncoder.cs index cecfd1c..4119e43 100644 --- a/src/IKVM.ByteCode/Writing/ClassConstantTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/ClassConstantTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -39,6 +40,32 @@ public ClassConstantTableEncoder Add(ClassConstantHandle clazz) return this; } + /// + /// Adds multiple classes to the table. + /// + /// + /// + public ClassConstantTableEncoder AddMany(ReadOnlySpan classes) + { + foreach (var i in classes) + Add(i); + + return this; + } + + /// + /// Adds multiple classes to the table. + /// + /// + /// + public ClassConstantTableEncoder AddMany(IEnumerable classes) + { + foreach (var i in classes) + Add(i); + + return this; + } + } } diff --git a/src/IKVM.ByteCode/Writing/ElementValueEncoder.cs b/src/IKVM.ByteCode/Writing/ElementValueEncoder.cs index 96ca097..d19e886 100644 --- a/src/IKVM.ByteCode/Writing/ElementValueEncoder.cs +++ b/src/IKVM.ByteCode/Writing/ElementValueEncoder.cs @@ -39,7 +39,7 @@ public void Encode(ElementValueRecord value) Char((IntegerConstantHandle)((ElementValueConstantValueRecord)value.Value).Handle); break; case ElementValueTag.Integer: - Int((IntegerConstantHandle)((ElementValueConstantValueRecord)value.Value).Handle); + Integer((IntegerConstantHandle)((ElementValueConstantValueRecord)value.Value).Handle); break; case ElementValueTag.Short: Short((IntegerConstantHandle)((ElementValueConstantValueRecord)value.Value).Handle); @@ -69,7 +69,7 @@ public void Encode(ElementValueRecord value) Annotation(e => e.Encode(((ElementValueAnnotationValueRecord)value.Value).Annotation)); break; case ElementValueTag.Array: - Array(e => e.Encode(((ElementValueArrayValueRecord)value.Value).Values)); + Array(e => e.AddMany(((ElementValueArrayValueRecord)value.Value).Values)); break; } } @@ -138,7 +138,7 @@ public void Float(FloatConstantHandle constantValue) /// Constant of the primitive type int as the value of this element-value pair. /// /// - public void Int(IntegerConstantHandle constantValue) + public void Integer(IntegerConstantHandle constantValue) { if (_count > 0) throw new InvalidOperationException("Only a single element value can be encoded by this encoder."); @@ -270,6 +270,7 @@ public void Array(Action arrayValue) arrayValue(new ElementValueTableEncoder(_builder)); _count++; } + } } diff --git a/src/IKVM.ByteCode/Writing/ElementValuePairTableEncoder.cs b/src/IKVM.ByteCode/Writing/ElementValuePairTableEncoder.cs index fda573c..24ddb57 100644 --- a/src/IKVM.ByteCode/Writing/ElementValuePairTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/ElementValuePairTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -19,7 +20,7 @@ public struct ElementValuePairTableEncoder /// /// Initializes a new instance. /// - /// + /// public ElementValuePairTableEncoder(BlobBuilder builder) { _builder = builder ?? throw new ArgumentNullException(nameof(builder)); @@ -27,15 +28,37 @@ public ElementValuePairTableEncoder(BlobBuilder builder) _count = 0; } + /// + /// Encodes an existing set of elements. + /// + /// + public ElementValuePairTableEncoder Add(ElementValuePairRecord element) + { + return Element(element.Name, e => e.Encode(element.Value)); + } + + /// + /// Encodes an existing set of elements. + /// + /// + public ElementValuePairTableEncoder AddMany(ReadOnlySpan elements) + { + foreach (var i in elements) + Add(i); + + return this; + } + /// /// Encodes an existing set of elements. /// /// - /// - public void Encode(ReadOnlySpan elements) + public ElementValuePairTableEncoder AddMany(IEnumerable elements) { foreach (var i in elements) - Element(i.Name, e => e.Encode(i.Value)); + Add(i); + + return this; } /// @@ -43,7 +66,7 @@ public void Encode(ReadOnlySpan elements) /// /// /// - public void Element(Utf8ConstantHandle elementName, Action elementValue) + public ElementValuePairTableEncoder Element(Utf8ConstantHandle elementName, Action elementValue) { if (elementValue is null) throw new ArgumentNullException(nameof(elementValue)); @@ -52,6 +75,128 @@ public void Element(Utf8ConstantHandle elementName, Action w.TryWriteU2(elementName.Index); elementValue(new ElementValueEncoder(_builder)); new ClassFormatWriter(_countBlob.GetBytes()).TryWriteU2(++_count); + return this; + } + + /// + /// Constant of the primitive type byte as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Byte(Utf8ConstantHandle elementName, IntegerConstantHandle constantValue) + { + return Element(elementName, e => e.Byte(constantValue)); + } + + /// + /// Constant of the primitive type char as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Char(Utf8ConstantHandle elementName, IntegerConstantHandle constantValue) + { + return Element(elementName, e => e.Char(constantValue)); + } + + /// + /// Constant of the primitive type double as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Double(Utf8ConstantHandle elementName, DoubleConstantHandle constantValue) + { + return Element(elementName, e => e.Double(constantValue)); + } + + /// + /// Constant of the primitive type float as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Float(Utf8ConstantHandle elementName, FloatConstantHandle constantValue) + { + return Element(elementName, e => e.Float(constantValue)); + } + + /// + /// Constant of the primitive type int as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Integer(Utf8ConstantHandle elementName, IntegerConstantHandle constantValue) + { + return Element(elementName, e => e.Integer(constantValue)); + } + + /// + /// Constant of the primitive type long as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Long(Utf8ConstantHandle elementName, LongConstantHandle constantValue) + { + return Element(elementName, e => e.Long(constantValue)); + } + + /// + /// Constant of the primitive type short as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Short(Utf8ConstantHandle elementName, IntegerConstantHandle constantValue) + { + return Element(elementName, e => e.Short(constantValue)); + } + + /// + /// Constant of the primitive type boolean as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Boolean(Utf8ConstantHandle elementName, IntegerConstantHandle constantValue) + { + return Element(elementName, e => e.Boolean(constantValue)); + } + + /// + /// Constant of the type String as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder String(Utf8ConstantHandle elementName, Utf8ConstantHandle constantValue) + { + return Element(elementName, e => e.String(constantValue)); + } + + /// + /// Denotes an enum constant as the value of this element-value pair. + /// + /// + /// + /// + public ElementValuePairTableEncoder Enum(Utf8ConstantHandle elementName, Utf8ConstantHandle typeName, Utf8ConstantHandle constName) + { + return Element(elementName, e => e.Enum(typeName, constName)); + } + + /// + /// Denotes a class literal as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Class(Utf8ConstantHandle elementName, Utf8ConstantHandle classInfo) + { + return Element(elementName, e => e.Class(classInfo)); + } + + /// + /// Denotes a "nested" annotation as the value of this element-value pair. + /// + /// + /// + public ElementValuePairTableEncoder Annotation(Utf8ConstantHandle elementName, Action annotationValue) + { + return Element(elementName, e => e.Annotation(annotationValue)); } } diff --git a/src/IKVM.ByteCode/Writing/ElementValueTableEncoder.cs b/src/IKVM.ByteCode/Writing/ElementValueTableEncoder.cs index 2b9d359..6e99c43 100644 --- a/src/IKVM.ByteCode/Writing/ElementValueTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/ElementValueTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -19,7 +20,7 @@ public struct ElementValueTableEncoder /// /// Initializes a new instance. /// - /// + /// public ElementValueTableEncoder(BlobBuilder builder) { _builder = builder ?? throw new ArgumentNullException(nameof(builder)); @@ -27,28 +28,151 @@ public ElementValueTableEncoder(BlobBuilder builder) _count = 0; } + /// + /// Adds an element value pair. + /// + /// + public ElementValueTableEncoder Add(Action value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + value(new ElementValueEncoder(_builder)); + new ClassFormatWriter(_countBlob.GetBytes()).TryWriteU2(++_count); + return this; + } + /// /// Encodes an existing set of elements. /// /// - /// - public void Encode(ReadOnlySpan elements) + public ElementValueTableEncoder AddMany(ReadOnlySpan elements) { foreach (var i in elements) - Element(e => e.Encode(i)); + Add(e => e.Encode(i)); + + return this; } /// - /// Adds an element value pair. + /// Encodes an existing set of elements. /// - /// - public void Element(Action value) + /// + public ElementValueTableEncoder AddMany(IEnumerable elements) { - if (value is null) - throw new ArgumentNullException(nameof(value)); + foreach (var i in elements) + Add(e => e.Encode(i)); - value(new ElementValueEncoder(_builder)); - new ClassFormatWriter(_countBlob.GetBytes()).TryWriteU2(++_count); + return this; + } + + /// + /// Constant of the primitive type byte as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Byte(IntegerConstantHandle constantValue) + { + return Add(e => e.Byte(constantValue)); + } + + /// + /// Constant of the primitive type char as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Char(IntegerConstantHandle constantValue) + { + return Add(e => e.Char(constantValue)); + } + + /// + /// Constant of the primitive type double as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Double(DoubleConstantHandle constantValue) + { + return Add(e => e.Double(constantValue)); + } + + /// + /// Constant of the primitive type float as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Float(FloatConstantHandle constantValue) + { + return Add(e => e.Float(constantValue)); + } + + /// + /// Constant of the primitive type int as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Integer(IntegerConstantHandle constantValue) + { + return Add(e => e.Integer(constantValue)); + } + + /// + /// Constant of the primitive type long as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Long(LongConstantHandle constantValue) + { + return Add(e => e.Long(constantValue)); + } + + /// + /// Constant of the primitive type short as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Short(IntegerConstantHandle constantValue) + { + return Add(e => e.Short(constantValue)); + } + + /// + /// Constant of the primitive type boolean as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Boolean(IntegerConstantHandle constantValue) + { + return Add(e => e.Boolean(constantValue)); + } + + /// + /// Constant of the type String as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder String(Utf8ConstantHandle constantValue) + { + return Add(e => e.String(constantValue)); + } + + /// + /// Denotes an enum constant as the value of this element-value pair. + /// + /// + /// + public ElementValueTableEncoder Enum(Utf8ConstantHandle typeName, Utf8ConstantHandle constName) + { + return Add(e => e.Enum(typeName, constName)); + } + + /// + /// Denotes a class literal as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Class(Utf8ConstantHandle classInfo) + { + return Add(e => e.Class(classInfo)); + } + + /// + /// Denotes a "nested" annotation as the value of this element-value pair. + /// + /// + public ElementValueTableEncoder Annotation(Action annotationValue) + { + return Add(e => e.Annotation(annotationValue)); } } diff --git a/src/IKVM.ByteCode/Writing/ExceptionTableEncoder.cs b/src/IKVM.ByteCode/Writing/ExceptionTableEncoder.cs index 8ebdc78..6e27b75 100644 --- a/src/IKVM.ByteCode/Writing/ExceptionTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/ExceptionTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -27,6 +28,42 @@ public ExceptionTableEncoder(BlobBuilder builder) _count = 0; } + /// + /// Adds an existing exception handler. + /// + /// + /// + public ExceptionTableEncoder Add(ExceptionHandlerRecord record) + { + return Exception(record.StartOffset, record.EndOffset, record.HandlerOffset, record.CatchType); + } + + /// + /// Adds existing exception handlers. + /// + /// + /// + public ExceptionTableEncoder AddMany(ReadOnlySpan records) + { + foreach (var i in records) + Add(i); + + return this; + } + + /// + /// Adds existing exception handlers. + /// + /// + /// + public ExceptionTableEncoder AddMany(IEnumerable records) + { + foreach (var i in records) + Add(i); + + return this; + } + /// /// Adds a new exception region at the specified program location. /// diff --git a/src/IKVM.ByteCode/Writing/InnerClassesTableEncoder.cs b/src/IKVM.ByteCode/Writing/InnerClassesTableEncoder.cs index fc5b9d0..f2d2947 100644 --- a/src/IKVM.ByteCode/Writing/InnerClassesTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/InnerClassesTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -25,7 +26,43 @@ public InnerClassesTableEncoder(BlobBuilder builder) } /// - /// Adds a package to the table. + /// Adds an existing inner class. + /// + /// + /// + public InnerClassesTableEncoder Add(InnerClassesAttributeItemRecord record) + { + return Add(record.InnerClass, record.OuterClass, record.InnerName, record.InnerClassAccessFlags); + } + + /// + /// Adds many existing inner classes. + /// + /// + /// + public InnerClassesTableEncoder AddMany(ReadOnlySpan records) + { + foreach (var i in records) + Add(i); + + return this; + } + + /// + /// Adds many existing inner classes. + /// + /// + /// + public InnerClassesTableEncoder AddMany(IEnumerable records) + { + foreach (var i in records) + Add(i); + + return this; + } + + /// + /// Adds a inner class to the table. /// /// public InnerClassesTableEncoder Add(ClassConstantHandle innerClass, ClassConstantHandle outerClass, Utf8ConstantHandle innerName, AccessFlag innerAccessFlags) diff --git a/src/IKVM.ByteCode/Writing/LineNumberTableEncoder.cs b/src/IKVM.ByteCode/Writing/LineNumberTableEncoder.cs index 0da1e02..92f289c 100644 --- a/src/IKVM.ByteCode/Writing/LineNumberTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/LineNumberTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -27,6 +28,42 @@ public LineNumberTableEncoder(BlobBuilder builder) _count = 0; } + /// + /// Adds an existing line number. + /// + /// + /// + public LineNumberTableEncoder Add(LineNumberTableAttributeItemRecord record) + { + return LineNumber(record.CodeOffset, record.LineNumber); + } + + /// + /// Adds an existing line number. + /// + /// + /// + public LineNumberTableEncoder AddMany(ReadOnlySpan records) + { + foreach (var i in records) + Add(i); + + return this; + } + + /// + /// Adds an existing line number. + /// + /// + /// + public LineNumberTableEncoder AddMany(IEnumerable records) + { + foreach (var i in records) + Add(i); + + return this; + } + /// /// Adds a new line number at the specified byte code offset. /// diff --git a/src/IKVM.ByteCode/Writing/LocalVariableTableEncoder.cs b/src/IKVM.ByteCode/Writing/LocalVariableTableEncoder.cs index 83dbcd1..40049f6 100644 --- a/src/IKVM.ByteCode/Writing/LocalVariableTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/LocalVariableTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -24,6 +25,42 @@ public LocalVariableTableEncoder(BlobBuilder builder) _count = 0; } + /// + /// Adds an existing local variable. + /// + /// + /// + public LocalVariableTableEncoder Add(LocalVariableTableAttributeItemRecord record) + { + return LocalVar(record.CodeOffset, record.CodeLength, record.Name, record.Descriptor, record.Index); + } + + /// + /// Adds many existing local variables. + /// + /// + /// + public LocalVariableTableEncoder AddMany(ReadOnlySpan records) + { + foreach (var i in records) + Add(i); + + return this; + } + + /// + /// Adds many existing local variables. + /// + /// + /// + public LocalVariableTableEncoder AddMany(IEnumerable records) + { + foreach (var i in records) + Add(i); + + return this; + } + /// /// Adds a new local variable. /// diff --git a/src/IKVM.ByteCode/Writing/LocalVariableTargetTableEncoder.cs b/src/IKVM.ByteCode/Writing/LocalVariableTargetTableEncoder.cs index 62fcce6..c92790c 100644 --- a/src/IKVM.ByteCode/Writing/LocalVariableTargetTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/LocalVariableTargetTableEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using IKVM.ByteCode.Buffers; using IKVM.ByteCode.Parsing; @@ -25,22 +26,36 @@ public LocalVariableTargetTableEncoder(BlobBuilder builder) } /// - /// Encodes an existing local variable target table record. + /// Encodes an existing local variable target. /// - /// - public void Encode(LocalVariableTargetTableRecord table) + /// + public LocalVariableTargetTableEncoder Add(LocalVariableTargetTableItemRecord target) { - foreach (var i in table.Items) - Encode(i); + return LocalVar(target.Start, target.Length, target.Index); } /// - /// Encodes an existing local variable target. + /// Adds many existing local variable targets. /// - /// - public void Encode(LocalVariableTargetTableItemRecord target) + /// + public LocalVariableTargetTableEncoder AddMany(ReadOnlySpan records) { - LocalVar(target.Start, target.Length, target.Index); + foreach (var i in records) + Add(i); + + return this; + } + + /// + /// Adds many existing local variable targets. + /// + /// + public LocalVariableTargetTableEncoder AddMany(IEnumerable records) + { + foreach (var i in records) + Add(i); + + return this; } /// diff --git a/src/IKVM.ByteCode/Writing/TypeAnnotationEncoder.cs b/src/IKVM.ByteCode/Writing/TypeAnnotationEncoder.cs index 7c70952..acba10a 100644 --- a/src/IKVM.ByteCode/Writing/TypeAnnotationEncoder.cs +++ b/src/IKVM.ByteCode/Writing/TypeAnnotationEncoder.cs @@ -33,34 +33,34 @@ public void Encode(TypeAnnotationRecord record) switch (record.Target) { case TypeParameterTargetRecord target: - TypeParameterTarget(record.TargetType, target.ParameterIndex, e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + TypeParameterTarget(record.TargetType, target.ParameterIndex, e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; case SuperTypeTargetRecord target: - SuperTypeTarget(record.TargetType, target.SuperTypeIndex, e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + SuperTypeTarget(record.TargetType, target.SuperTypeIndex, e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; case TypeParameterBoundTargetRecord target: - TypeParameterBoundTarget(record.TargetType, target.ParameterIndex, target.BoundIndex, e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + TypeParameterBoundTarget(record.TargetType, target.ParameterIndex, target.BoundIndex, e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; case EmptyTargetRecord target: - EmptyTarget(record.TargetType, e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + EmptyTarget(record.TargetType, e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; case FormalParameterTargetRecord target: - FormalParameterTarget(record.TargetType, target.ParameterIndex, e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + FormalParameterTarget(record.TargetType, target.ParameterIndex, e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; case ThrowsTargetRecord target: - ThrowsTarget(record.TargetType, target.ThrowsTypeIndex, e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + ThrowsTarget(record.TargetType, target.ThrowsTypeIndex, e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; case LocalVariableTargetTableRecord target: - LocalVarTarget(record.TargetType, e => e.Encode(target), e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + LocalVarTarget(record.TargetType, e => e.AddMany(target), e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; case CatchTargetRecord target: - CatchTarget(record.TargetType, target.ExceptionTableIndex, e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + CatchTarget(record.TargetType, target.ExceptionTableIndex, e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; case OffsetTargetRecord target: - OffsetTarget(record.TargetType, target.Offset, e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + OffsetTarget(record.TargetType, target.Offset, e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; case TypeArgumentTargetRecord target: - TypeArgumentTarget(record.TargetType, target.Offset, target.TypeArgumentIndex, e => e.Encode(record.TargetPath), record.Type, e => e.Encode(record.Elements)); + TypeArgumentTarget(record.TargetType, target.Offset, target.TypeArgumentIndex, e => e.Encode(record.TargetPath), record.Type, e => e.AddMany(record.Elements)); break; default: throw new InvalidOperationException("Invalid type annotation."); diff --git a/src/IKVM.ByteCode/Writing/TypeAnnotationTableEncoder.cs b/src/IKVM.ByteCode/Writing/TypeAnnotationTableEncoder.cs index 903cbb0..ce15a1e 100644 --- a/src/IKVM.ByteCode/Writing/TypeAnnotationTableEncoder.cs +++ b/src/IKVM.ByteCode/Writing/TypeAnnotationTableEncoder.cs @@ -31,9 +31,9 @@ public TypeAnnotationTableEncoder(BlobBuilder builder) /// Encodes an existing annotation. /// /// - public void Encode(TypeAnnotationRecord record) + public TypeAnnotationTableEncoder Encode(TypeAnnotationRecord record) { - Annotation(e => e.Encode(record)); + return Annotation(e => e.Encode(record)); } /// @@ -42,6 +42,9 @@ public void Encode(TypeAnnotationRecord record) /// public TypeAnnotationTableEncoder Annotation(Action annotation) { + if (annotation is null) + throw new ArgumentNullException(nameof(annotation)); + annotation(new TypeAnnotationEncoder(_builder)); new ClassFormatWriter(_countBlob.GetBytes()).TryWriteU2(++_count); return this; diff --git a/src/IKVM.ByteCode/Writing/TypePathEncoder.cs b/src/IKVM.ByteCode/Writing/TypePathEncoder.cs index 175a7de..ece54a4 100644 --- a/src/IKVM.ByteCode/Writing/TypePathEncoder.cs +++ b/src/IKVM.ByteCode/Writing/TypePathEncoder.cs @@ -28,77 +28,76 @@ public TypePathEncoder(BlobBuilder builder) /// Encodes an existing type path. /// /// - public void Encode(TypePathRecord targetPath) + public TypePathEncoder Encode(TypePathRecord targetPath) { foreach (var i in targetPath.Path) Encode(i); + + return this; } /// /// Encodes an existing type path item. /// /// - public void Encode(TypePathItemRecord item) + public TypePathEncoder Encode(TypePathItemRecord item) { - switch (item.Kind) + return item.Kind switch { - case TypePathKind.Array: - Array(); - break; - case TypePathKind.InnerType: - InnerType(); - break; - case TypePathKind.Wildcard: - Wildcard(); - break; - case TypePathKind.TypeArgument: - TypeArgument(item.ArgumentIndex); - break; - } + TypePathKind.Array => Array(), + TypePathKind.InnerType => InnerType(), + TypePathKind.Wildcard => Wildcard(), + TypePathKind.TypeArgument => TypeArgument(item.ArgumentIndex), + _ => throw new ArgumentException("Invalid path kind.", nameof(item)), + }; } /// /// Annotation is deeper in an array type. /// - public void Array() + public TypePathEncoder Array() { var w = new ClassFormatWriter(_builder.ReserveBytes(ClassFormatWriter.U1 + ClassFormatWriter.U1).GetBytes()); w.TryWriteU1(0); w.TryWriteU1(0); new ClassFormatWriter(_countBlob.GetBytes()).TryWriteU1(++_count); + return this; } /// /// Annotation is deeper in a nested type. /// - public void InnerType() + public TypePathEncoder InnerType() { var w = new ClassFormatWriter(_builder.ReserveBytes(ClassFormatWriter.U1 + ClassFormatWriter.U1).GetBytes()); w.TryWriteU1(1); w.TryWriteU1(0); new ClassFormatWriter(_countBlob.GetBytes()).TryWriteU1(++_count); + return this; } /// /// Annotation is on the bound of a wildcard type argument of a parameterized type. /// - public void Wildcard() + public TypePathEncoder Wildcard() { var w = new ClassFormatWriter(_builder.ReserveBytes(ClassFormatWriter.U1 + ClassFormatWriter.U1).GetBytes()); w.TryWriteU1(2); w.TryWriteU1(0); new ClassFormatWriter(_countBlob.GetBytes()).TryWriteU1(++_count); + return this; } /// /// Annotation is on the bound of a wildcard type argument of a parameterized type. /// - public void TypeArgument(byte index) + public TypePathEncoder TypeArgument(byte index) { var w = new ClassFormatWriter(_builder.ReserveBytes(ClassFormatWriter.U1 + ClassFormatWriter.U1).GetBytes()); w.TryWriteU1(3); w.TryWriteU1(index); new ClassFormatWriter(_countBlob.GetBytes()).TryWriteU1(++_count); + return this; } }