private void AssertGenerated_(string src, string expectedGenerated)
        {
            var structure = SchemaTestUtil.Parse(src);

            Assert.IsEmpty(structure.Diagnostics);

            var actualGenerated = new SchemaReaderGenerator().Generate(structure);

            Assert.AreEqual(expectedGenerated, actualGenerated.ReplaceLineEndings());
        }
        private static void ReadPrimitive_(
            ICurlyBracketStringBuilder cbsb,
            ITypeSymbol sourceSymbol,
            ISchemaMember member)
        {
            var primitiveType = member.MemberType as IPrimitiveMemberType;

            if (primitiveType.PrimitiveType == SchemaPrimitiveType.BOOLEAN)
            {
                SchemaReaderGenerator.ReadBoolean_(cbsb, member);
                return;
            }

            var readType = SchemaGeneratorUtil.GetPrimitiveLabel(
                primitiveType.UseAltFormat
              ? SchemaPrimitiveTypesUtil.ConvertNumberToPrimitive(
                    primitiveType.AltFormat)
              : primitiveType.PrimitiveType);

            var needToCast = primitiveType.UseAltFormat &&
                             primitiveType.PrimitiveType !=
                             SchemaPrimitiveTypesUtil.GetUnderlyingPrimitiveType(
                SchemaPrimitiveTypesUtil.ConvertNumberToPrimitive(
                    primitiveType.AltFormat));

            if (!primitiveType.IsReadonly)
            {
                var castText = "";
                if (needToCast)
                {
                    var castType =
                        primitiveType.PrimitiveType == SchemaPrimitiveType.ENUM
                  ? SymbolTypeUtil.GetQualifiedNameFromCurrentSymbol(
                            sourceSymbol,
                            primitiveType.TypeSymbol)
                  : primitiveType.TypeSymbol.Name;
                    castText = $"({castType}) ";
                }
                cbsb.WriteLine(
                    $"this.{member.Name} = {castText}er.Read{readType}();");
            }
            else
            {
                var castText = "";
                if (needToCast)
                {
                    var castType =
                        SchemaGeneratorUtil.GetTypeName(primitiveType.AltFormat);
                    castText = $"({castType}) ";
                }
                cbsb.WriteLine($"er.Assert{readType}({castText}this.{member.Name});");
            }
        }
        public string Generate(ISchemaStructure structure)
        {
            var typeSymbol = structure.TypeSymbol;

            var typeNamespace = SymbolTypeUtil.MergeContainingNamespaces(typeSymbol);

            var declaringTypes =
                SymbolTypeUtil.GetDeclaringTypesDownward(typeSymbol);

            var cbsb = new CurlyBracketStringBuilder();

            cbsb.WriteLine("using System;")
            .WriteLine("using System.IO;");

            // TODO: Handle fancier cases here
            cbsb.EnterBlock($"namespace {typeNamespace}");
            foreach (var declaringType in declaringTypes)
            {
                cbsb.EnterBlock(SymbolTypeUtil.GetQualifiersAndNameFor(declaringType));
            }
            cbsb.EnterBlock(SymbolTypeUtil.GetQualifiersAndNameFor(typeSymbol));

            cbsb.EnterBlock("public void Read(EndianBinaryReader er)");
            foreach (var member in structure.Members)
            {
                SchemaReaderGenerator.ReadMember_(cbsb, typeSymbol, member);
            }
            cbsb.ExitBlock();

            // TODO: Handle fancier cases here

            // type
            cbsb.ExitBlock();

            // parent types
            foreach (var declaringType in declaringTypes)
            {
                cbsb.ExitBlock();
            }

            // namespace
            cbsb.ExitBlock();

            var generatedCode = cbsb.ToString();

            return(generatedCode);
        }
        private static void ReadMember_(
            ICurlyBracketStringBuilder cbsb,
            ITypeSymbol sourceSymbol,
            ISchemaMember member)
        {
            if (member.IsPosition)
            {
                if (member.MemberType.IsReadonly)
                {
                    cbsb.WriteLine($"er.AssertPosition(this.{member.Name});");
                }
                else
                {
                    cbsb.WriteLine($"this.{member.Name} = er.Position;");
                }
                return;
            }

            SchemaReaderGenerator.Align_(cbsb, member);

            // TODO: How to handle both offset & if boolean together?

            var offset = member.Offset;

            if (offset != null)
            {
                cbsb.EnterBlock()
                .WriteLine("var tempLocation = er.Position;")
                .WriteLine(
                    $"er.Position = this.{offset.StartIndexName.Name} + this.{offset.OffsetName.Name};");
            }

            var ifBoolean          = member.IfBoolean;
            var immediateIfBoolean =
                ifBoolean?.SourceType == IfBooleanSourceType.IMMEDIATE_VALUE;

            if (immediateIfBoolean)
            {
                cbsb.EnterBlock();
            }
            if (ifBoolean != null)
            {
                if (ifBoolean.SourceType == IfBooleanSourceType.IMMEDIATE_VALUE)
                {
                    var booleanNumberType =
                        SchemaPrimitiveTypesUtil.ConvertIntToNumber(
                            ifBoolean.ImmediateBooleanType);
                    var booleanPrimitiveType =
                        SchemaPrimitiveTypesUtil.ConvertNumberToPrimitive(
                            booleanNumberType);
                    var booleanPrimitiveLabel =
                        SchemaGeneratorUtil.GetPrimitiveLabel(booleanPrimitiveType);
                    cbsb.WriteLine($"var b = er.Read{booleanPrimitiveLabel}() != 0;")
                    .EnterBlock("if (b)");
                }
                else
                {
                    cbsb.EnterBlock($"if (this.{ifBoolean.BooleanMember.Name})");
                }

                if (member.MemberType is not IPrimitiveMemberType)
                {
                    cbsb.WriteLine(
                        $"this.{member.Name} = new {SymbolTypeUtil.GetQualifiedNameFromCurrentSymbol(sourceSymbol, member.MemberType.TypeSymbol)}();");
                }
            }

            var memberType = member.MemberType;

            if (memberType is IGenericMemberType genericMemberType)
            {
                memberType = genericMemberType.ConstraintType;
            }

            switch (memberType)
            {
            case IPrimitiveMemberType: {
                SchemaReaderGenerator.ReadPrimitive_(cbsb, sourceSymbol, member);
                break;
            }

            case IStringType: {
                SchemaReaderGenerator.ReadString_(cbsb, member);
                break;
            }

            case IStructureMemberType structureMemberType: {
                SchemaReaderGenerator.ReadStructure_(cbsb,
                                                     structureMemberType,
                                                     member);
                break;
            }

            case ISequenceMemberType: {
                SchemaReaderGenerator.ReadArray_(cbsb, sourceSymbol, member);
                break;
            }

            default: {
                // Anything that makes it down here probably isn't meant to be read.
                throw new NotImplementedException();
            }
            }

            if (ifBoolean != null)
            {
                cbsb.ExitBlock()
                .EnterBlock("else")
                .WriteLine($"this.{member.Name} = null;")
                .ExitBlock();
                if (immediateIfBoolean)
                {
                    cbsb.ExitBlock();
                }
            }

            if (offset != null)
            {
                cbsb.WriteLine("er.Position = tempLocation;")
                .ExitBlock();
            }
        }
        private static void ReadArray_(
            ICurlyBracketStringBuilder cbsb,
            ITypeSymbol sourceSymbol,
            ISchemaMember member)
        {
            var arrayType = member.MemberType as ISequenceMemberType;

            if (arrayType.LengthSourceType != SequenceLengthSourceType.CONST)
            {
                var isImmediate =
                    arrayType.LengthSourceType ==
                    SequenceLengthSourceType.IMMEDIATE_VALUE;

                var lengthName =
                    isImmediate ? "c" : $"this.{arrayType.LengthMember!.Name}";

                if (isImmediate)
                {
                    var readType = SchemaGeneratorUtil.GetIntLabel(
                        arrayType.ImmediateLengthType);
                    cbsb.EnterBlock()
                    .WriteLine($"var {lengthName} = er.Read{readType}();");
                }

                cbsb.EnterBlock($"if ({lengthName} < 0)")
                .WriteLine(
                    $"throw new Exception(\"Expected length to be nonnegative!\");")
                .ExitBlock();

                var qualifiedElementName =
                    SymbolTypeUtil.GetQualifiedNameFromCurrentSymbol(
                        sourceSymbol,
                        arrayType.ElementType.TypeSymbol);
                var hasReferenceElements =
                    arrayType.ElementType is IStructureMemberType {
                    IsReferenceType : true
                };

                // TODO: Handle readonly lists, can't be expanded like this!
                if (arrayType.SequenceType == SequenceType.LIST)
                {
                    cbsb.EnterBlock($"if (this.{member.Name}.Count < {lengthName})");
                    if (hasReferenceElements)
                    {
                        cbsb.WriteLine(
                            $"this.{member.Name}.Add(new {qualifiedElementName}());");
                    }
                    else
                    {
                        cbsb.WriteLine($"this.{member.Name}.Add(default);");
                    }
                    cbsb.ExitBlock();

                    cbsb.EnterBlock(
                        $"while (this.{member.Name}.Count > {lengthName})")
                    .WriteLine($"this.{member.Name}.RemoveAt(0);")
                    .ExitBlock();
                }
                else
                {
                    cbsb.WriteLine(
                        $"this.{member.Name} = new {qualifiedElementName}[{lengthName}];");

                    if (hasReferenceElements)
                    {
                        cbsb.EnterBlock($"for (var i = 0; i < {lengthName}; ++i)")
                        .WriteLine(
                            $"this.{member.Name}[i] = new {qualifiedElementName}();")
                        .ExitBlock();
                    }
                }

                if (isImmediate)
                {
                    cbsb.ExitBlock();
                }
            }

            SchemaReaderGenerator.ReadIntoArray_(cbsb, sourceSymbol, member);
        }