diff --git a/Tomlet/Attributes/TomlNonSerializedAttribute.cs b/Tomlet/Attributes/TomlNonSerializedAttribute.cs index c604fee..18cafd8 100644 --- a/Tomlet/Attributes/TomlNonSerializedAttribute.cs +++ b/Tomlet/Attributes/TomlNonSerializedAttribute.cs @@ -1,9 +1,8 @@ using System; -namespace Tomlet.Attributes +namespace Tomlet.Attributes; + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public class TomlNonSerializedAttribute : Attribute { - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public class TomlNonSerializedAttribute : Attribute - { - } -} +} \ No newline at end of file diff --git a/Tomlet/Exceptions/InvalidTomlDateTimeException.cs b/Tomlet/Exceptions/InvalidTomlDateTimeException.cs index 9d73c06..d83a26d 100644 --- a/Tomlet/Exceptions/InvalidTomlDateTimeException.cs +++ b/Tomlet/Exceptions/InvalidTomlDateTimeException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class InvalidTomlDateTimeException : TomlExceptionWithLine - { - private readonly string _inputString; +namespace Tomlet.Exceptions; - public InvalidTomlDateTimeException(int lineNumber, string inputString) : base(lineNumber) - { - _inputString = inputString; - } +public class InvalidTomlDateTimeException : TomlExceptionWithLine +{ + private readonly string _inputString; - public override string Message => $"Found an invalid TOML date/time string '{_inputString}' on line {LineNumber}"; + public InvalidTomlDateTimeException(int lineNumber, string inputString) : base(lineNumber) + { + _inputString = inputString; } + + public override string Message => $"Found an invalid TOML date/time string '{_inputString}' on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/InvalidTomlEscapeException.cs b/Tomlet/Exceptions/InvalidTomlEscapeException.cs index 6229772..9d69898 100644 --- a/Tomlet/Exceptions/InvalidTomlEscapeException.cs +++ b/Tomlet/Exceptions/InvalidTomlEscapeException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class InvalidTomlEscapeException : TomlExceptionWithLine - { - private readonly string _escapeSequence; +namespace Tomlet.Exceptions; - public InvalidTomlEscapeException(int lineNumber, string escapeSequence) : base(lineNumber) - { - _escapeSequence = escapeSequence; - } +public class InvalidTomlEscapeException : TomlExceptionWithLine +{ + private readonly string _escapeSequence; - public override string Message => $"Found an invalid escape sequence '\\{_escapeSequence}' on line {LineNumber}"; + public InvalidTomlEscapeException(int lineNumber, string escapeSequence) : base(lineNumber) + { + _escapeSequence = escapeSequence; } + + public override string Message => $"Found an invalid escape sequence '\\{_escapeSequence}' on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/InvalidTomlInlineTableException.cs b/Tomlet/Exceptions/InvalidTomlInlineTableException.cs index 91690b0..0ef81d1 100644 --- a/Tomlet/Exceptions/InvalidTomlInlineTableException.cs +++ b/Tomlet/Exceptions/InvalidTomlInlineTableException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class InvalidTomlInlineTableException : TomlExceptionWithLine { - public class InvalidTomlInlineTableException : TomlExceptionWithLine + public InvalidTomlInlineTableException(int lineNumber, TomlException cause) : base(lineNumber, cause) { - public InvalidTomlInlineTableException(int lineNumber, TomlException cause) : base(lineNumber, cause) - { - } - - public override string Message => $"Found an invalid inline TOML table on line {LineNumber}. See further down for cause."; } + + public override string Message => $"Found an invalid inline TOML table on line {LineNumber}. See further down for cause."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/InvalidTomlKeyException.cs b/Tomlet/Exceptions/InvalidTomlKeyException.cs index a300c69..681c889 100644 --- a/Tomlet/Exceptions/InvalidTomlKeyException.cs +++ b/Tomlet/Exceptions/InvalidTomlKeyException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class InvalidTomlKeyException : TomlException - { - private readonly string _key; +namespace Tomlet.Exceptions; - public InvalidTomlKeyException(string key) - { - _key = key; - } +public class InvalidTomlKeyException : TomlException +{ + private readonly string _key; - public override string Message => $"The string |{_key}| (between the two bars) contains at least one of both a double quote and a single quote, so it cannot be used for a TOML key."; + public InvalidTomlKeyException(string key) + { + _key = key; } + + public override string Message => $"The string |{_key}| (between the two bars) contains at least one of both a double quote and a single quote, so it cannot be used for a TOML key."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/InvalidTomlNumberException.cs b/Tomlet/Exceptions/InvalidTomlNumberException.cs index 31fe61a..9afdc83 100644 --- a/Tomlet/Exceptions/InvalidTomlNumberException.cs +++ b/Tomlet/Exceptions/InvalidTomlNumberException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class InvalidTomlNumberException : TomlExceptionWithLine - { - private readonly string _input; +namespace Tomlet.Exceptions; - public InvalidTomlNumberException(int lineNumber, string input) : base(lineNumber) - { - _input = input; - } +public class InvalidTomlNumberException : TomlExceptionWithLine +{ + private readonly string _input; - public override string Message => $"While reading input line {LineNumber}, found an invalid number literal '{_input}'"; + public InvalidTomlNumberException(int lineNumber, string input) : base(lineNumber) + { + _input = input; } + + public override string Message => $"While reading input line {LineNumber}, found an invalid number literal '{_input}'"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/MissingIntermediateInTomlTableArraySpecException.cs b/Tomlet/Exceptions/MissingIntermediateInTomlTableArraySpecException.cs index 822cc11..546b5ae 100644 --- a/Tomlet/Exceptions/MissingIntermediateInTomlTableArraySpecException.cs +++ b/Tomlet/Exceptions/MissingIntermediateInTomlTableArraySpecException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class MissingIntermediateInTomlTableArraySpecException : TomlExceptionWithLine - { - private readonly string _missing; +namespace Tomlet.Exceptions; - public MissingIntermediateInTomlTableArraySpecException(int lineNumber, string missing) : base(lineNumber) - { - _missing = missing; - } +public class MissingIntermediateInTomlTableArraySpecException : TomlExceptionWithLine +{ + private readonly string _missing; - public override string Message => $"Missing intermediate definition for {_missing} in table-array specification on line {LineNumber}. This is undefined behavior, and I chose to define it as an error."; + public MissingIntermediateInTomlTableArraySpecException(int lineNumber, string missing) : base(lineNumber) + { + _missing = missing; } + + public override string Message => $"Missing intermediate definition for {_missing} in table-array specification on line {LineNumber}. This is undefined behavior, and I chose to define it as an error."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/NewLineInTomlInlineTableException.cs b/Tomlet/Exceptions/NewLineInTomlInlineTableException.cs index dd54e9e..a405aa3 100644 --- a/Tomlet/Exceptions/NewLineInTomlInlineTableException.cs +++ b/Tomlet/Exceptions/NewLineInTomlInlineTableException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class NewLineInTomlInlineTableException : TomlExceptionWithLine { - public class NewLineInTomlInlineTableException : TomlExceptionWithLine + public NewLineInTomlInlineTableException(int lineNumber) : base(lineNumber) { - public NewLineInTomlInlineTableException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => "Found a new-line character within a TOML inline table. This is not allowed."; } + + public override string Message => "Found a new-line character within a TOML inline table. This is not allowed."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/NoTomlKeyException.cs b/Tomlet/Exceptions/NoTomlKeyException.cs index 32688bf..21d7854 100644 --- a/Tomlet/Exceptions/NoTomlKeyException.cs +++ b/Tomlet/Exceptions/NoTomlKeyException.cs @@ -1,9 +1,8 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class NoTomlKeyException : TomlExceptionWithLine { - public class NoTomlKeyException : TomlExceptionWithLine - { - public NoTomlKeyException(int lineNumber) : base(lineNumber) { } + public NoTomlKeyException(int lineNumber) : base(lineNumber) { } - public override string Message => $"Expected a TOML key on line {LineNumber}, but found an equals sign ('=')."; - } + public override string Message => $"Expected a TOML key on line {LineNumber}, but found an equals sign ('=')."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TimeOffsetOnTomlDateOrTimeException.cs b/Tomlet/Exceptions/TimeOffsetOnTomlDateOrTimeException.cs index c2ab39f..db6e770 100644 --- a/Tomlet/Exceptions/TimeOffsetOnTomlDateOrTimeException.cs +++ b/Tomlet/Exceptions/TimeOffsetOnTomlDateOrTimeException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TimeOffsetOnTomlDateOrTimeException : TomlExceptionWithLine - { - private readonly string _tzString; +namespace Tomlet.Exceptions; - public TimeOffsetOnTomlDateOrTimeException(int lineNumber, string tzString) : base(lineNumber) - { - _tzString = tzString; - } +public class TimeOffsetOnTomlDateOrTimeException : TomlExceptionWithLine +{ + private readonly string _tzString; - public override string Message => $"Found a time offset string {_tzString} in a partial datetime on line {LineNumber}. This is not allowed - either specify both the date and the time, or remove the offset specifier."; + public TimeOffsetOnTomlDateOrTimeException(int lineNumber, string tzString) : base(lineNumber) + { + _tzString = tzString; } + + public override string Message => $"Found a time offset string {_tzString} in a partial datetime on line {LineNumber}. This is not allowed - either specify both the date and the time, or remove the offset specifier."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlArraySyntaxException.cs b/Tomlet/Exceptions/TomlArraySyntaxException.cs index a472dae..a2cb666 100644 --- a/Tomlet/Exceptions/TomlArraySyntaxException.cs +++ b/Tomlet/Exceptions/TomlArraySyntaxException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlArraySyntaxException : TomlExceptionWithLine - { - private readonly char _charFound; +namespace Tomlet.Exceptions; - public TomlArraySyntaxException(int lineNumber, char charFound) : base(lineNumber) - { - _charFound = charFound; - } +public class TomlArraySyntaxException : TomlExceptionWithLine +{ + private readonly char _charFound; - public override string Message => $"Expecting ',' or ']' after value in array on line {LineNumber}, found '{_charFound}'"; + public TomlArraySyntaxException(int lineNumber, char charFound) : base(lineNumber) + { + _charFound = charFound; } + + public override string Message => $"Expecting ',' or ']' after value in array on line {LineNumber}, found '{_charFound}'"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlContainsDottedKeyNonTableException.cs b/Tomlet/Exceptions/TomlContainsDottedKeyNonTableException.cs index 4676670..847c958 100644 --- a/Tomlet/Exceptions/TomlContainsDottedKeyNonTableException.cs +++ b/Tomlet/Exceptions/TomlContainsDottedKeyNonTableException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlContainsDottedKeyNonTableException : TomlException - { - internal readonly string Key; +namespace Tomlet.Exceptions; - public TomlContainsDottedKeyNonTableException(string key) - { - Key = key; - } +public class TomlContainsDottedKeyNonTableException : TomlException +{ + internal readonly string Key; - public override string Message => $"A call was made on a TOML table which attempted to access a sub-key of {Key}, but the value it refers to is not a table"; + public TomlContainsDottedKeyNonTableException(string key) + { + Key = key; } + + public override string Message => $"A call was made on a TOML table which attempted to access a sub-key of {Key}, but the value it refers to is not a table"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlDateTimeMissingSeparatorException.cs b/Tomlet/Exceptions/TomlDateTimeMissingSeparatorException.cs index 9c61e67..dbfaf26 100644 --- a/Tomlet/Exceptions/TomlDateTimeMissingSeparatorException.cs +++ b/Tomlet/Exceptions/TomlDateTimeMissingSeparatorException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TomlDateTimeMissingSeparatorException : TomlExceptionWithLine { - public class TomlDateTimeMissingSeparatorException : TomlExceptionWithLine + public TomlDateTimeMissingSeparatorException(int lineNumber) : base(lineNumber) { - public TomlDateTimeMissingSeparatorException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found a date-time on line {LineNumber} which is missing a separator (T, t, or a space) between the date and time."; } + + public override string Message => $"Found a date-time on line {LineNumber} which is missing a separator (T, t, or a space) between the date and time."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlDateTimeUnnecessarySeparatorException.cs b/Tomlet/Exceptions/TomlDateTimeUnnecessarySeparatorException.cs index 67d495d..a1aa9bb 100644 --- a/Tomlet/Exceptions/TomlDateTimeUnnecessarySeparatorException.cs +++ b/Tomlet/Exceptions/TomlDateTimeUnnecessarySeparatorException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TomlDateTimeUnnecessarySeparatorException : TomlExceptionWithLine { - public class TomlDateTimeUnnecessarySeparatorException : TomlExceptionWithLine + public TomlDateTimeUnnecessarySeparatorException(int lineNumber) : base(lineNumber) { - public TomlDateTimeUnnecessarySeparatorException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found an unnecessary date-time separator (T, t, or a space) in a date or time on line {LineNumber}"; } + + public override string Message => $"Found an unnecessary date-time separator (T, t, or a space) in a date or time on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlDottedKeyException.cs b/Tomlet/Exceptions/TomlDottedKeyException.cs index 97ffd95..fea3a2a 100644 --- a/Tomlet/Exceptions/TomlDottedKeyException.cs +++ b/Tomlet/Exceptions/TomlDottedKeyException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlDottedKeyException : TomlException - { - private readonly string _key; +namespace Tomlet.Exceptions; - public TomlDottedKeyException(string key) - { - _key = key; - } +public class TomlDottedKeyException : TomlException +{ + private readonly string _key; - public override string Message => $"Tried to redefine key {_key} as a table (by way of a dotted key) when it's already defined as not being a table."; + public TomlDottedKeyException(string key) + { + _key = key; } + + public override string Message => $"Tried to redefine key {_key} as a table (by way of a dotted key) when it's already defined as not being a table."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlDottedKeyParserException.cs b/Tomlet/Exceptions/TomlDottedKeyParserException.cs index d47799b..4da357c 100644 --- a/Tomlet/Exceptions/TomlDottedKeyParserException.cs +++ b/Tomlet/Exceptions/TomlDottedKeyParserException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlDottedKeyParserException : TomlExceptionWithLine - { - private readonly string _key; +namespace Tomlet.Exceptions; - public TomlDottedKeyParserException(int lineNumber, string key) : base(lineNumber) - { - _key = key; - } +public class TomlDottedKeyParserException : TomlExceptionWithLine +{ + private readonly string _key; - public override string Message => $"Tried to redefine key {_key} as a table (by way of a dotted key on line {LineNumber}) when it's already defined as not being a table."; + public TomlDottedKeyParserException(int lineNumber, string key) : base(lineNumber) + { + _key = key; } + + public override string Message => $"Tried to redefine key {_key} as a table (by way of a dotted key on line {LineNumber}) when it's already defined as not being a table."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlEndOfFileException.cs b/Tomlet/Exceptions/TomlEndOfFileException.cs index f5afa04..b3b93e6 100644 --- a/Tomlet/Exceptions/TomlEndOfFileException.cs +++ b/Tomlet/Exceptions/TomlEndOfFileException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TomlEndOfFileException : TomlExceptionWithLine { - public class TomlEndOfFileException : TomlExceptionWithLine + public TomlEndOfFileException(int lineNumber) : base(lineNumber) { - public TomlEndOfFileException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found unexpected EOF on line {LineNumber} when parsing TOML file"; } + + public override string Message => $"Found unexpected EOF on line {LineNumber} when parsing TOML file"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlEnumParseException.cs b/Tomlet/Exceptions/TomlEnumParseException.cs index 6ae5116..00ac299 100644 --- a/Tomlet/Exceptions/TomlEnumParseException.cs +++ b/Tomlet/Exceptions/TomlEnumParseException.cs @@ -1,18 +1,17 @@ using System; -namespace Tomlet.Exceptions -{ - public class TomlEnumParseException : TomlException - { - private string _valueName; - private Type _enumType; +namespace Tomlet.Exceptions; - public TomlEnumParseException(string valueName, Type enumType) - { - _valueName = valueName; - _enumType = enumType; - } +public class TomlEnumParseException : TomlException +{ + private string _valueName; + private Type _enumType; - public override string Message => $"Could not find enum value by name \"{_valueName}\" in enum class {_enumType} while deserializing."; + public TomlEnumParseException(string valueName, Type enumType) + { + _valueName = valueName; + _enumType = enumType; } + + public override string Message => $"Could not find enum value by name \"{_valueName}\" in enum class {_enumType} while deserializing."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlException.cs b/Tomlet/Exceptions/TomlException.cs index 3370622..14afa95 100644 --- a/Tomlet/Exceptions/TomlException.cs +++ b/Tomlet/Exceptions/TomlException.cs @@ -1,15 +1,14 @@ using System; -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public abstract class TomlException : Exception { - public abstract class TomlException : Exception + protected TomlException() { - protected TomlException() - { - } + } - protected TomlException(Exception cause) : base("", cause) - { - } + protected TomlException(Exception cause) : base("", cause) + { } } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlExceptionWithLine.cs b/Tomlet/Exceptions/TomlExceptionWithLine.cs index e825f1e..19bf176 100644 --- a/Tomlet/Exceptions/TomlExceptionWithLine.cs +++ b/Tomlet/Exceptions/TomlExceptionWithLine.cs @@ -1,19 +1,18 @@ using System; -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public abstract class TomlExceptionWithLine : TomlException { - public abstract class TomlExceptionWithLine : TomlException - { - protected int LineNumber; + protected int LineNumber; - protected TomlExceptionWithLine(int lineNumber) - { - LineNumber = lineNumber; - } + protected TomlExceptionWithLine(int lineNumber) + { + LineNumber = lineNumber; + } - protected TomlExceptionWithLine(int lineNumber, Exception cause) : base(cause) - { - LineNumber = lineNumber; - } + protected TomlExceptionWithLine(int lineNumber, Exception cause) : base(cause) + { + LineNumber = lineNumber; } } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlFieldTypeMismatchException.cs b/Tomlet/Exceptions/TomlFieldTypeMismatchException.cs index 6fbe9f9..8e20722 100644 --- a/Tomlet/Exceptions/TomlFieldTypeMismatchException.cs +++ b/Tomlet/Exceptions/TomlFieldTypeMismatchException.cs @@ -1,19 +1,18 @@ using System; using System.Reflection; -namespace Tomlet.Exceptions -{ - public class TomlFieldTypeMismatchException : TomlTypeMismatchException - { - private readonly Type _typeBeingInstantiated; - private readonly FieldInfo _fieldBeingDeserialized; +namespace Tomlet.Exceptions; - public TomlFieldTypeMismatchException(Type typeBeingInstantiated, FieldInfo fieldBeingDeserialized, TomlTypeMismatchException cause) : base(cause.ExpectedType, cause.ActualType, fieldBeingDeserialized.FieldType) - { - _typeBeingInstantiated = typeBeingInstantiated; - _fieldBeingDeserialized = fieldBeingDeserialized; - } +public class TomlFieldTypeMismatchException : TomlTypeMismatchException +{ + private readonly Type _typeBeingInstantiated; + private readonly FieldInfo _fieldBeingDeserialized; - public override string Message => $"While deserializing an object of type {_typeBeingInstantiated}, found field {_fieldBeingDeserialized.Name} expecting a type of {ExpectedTypeName}, but value in TOML was of type {ActualTypeName}"; + public TomlFieldTypeMismatchException(Type typeBeingInstantiated, FieldInfo fieldBeingDeserialized, TomlTypeMismatchException cause) : base(cause.ExpectedType, cause.ActualType, fieldBeingDeserialized.FieldType) + { + _typeBeingInstantiated = typeBeingInstantiated; + _fieldBeingDeserialized = fieldBeingDeserialized; } + + public override string Message => $"While deserializing an object of type {_typeBeingInstantiated}, found field {_fieldBeingDeserialized.Name} expecting a type of {ExpectedTypeName}, but value in TOML was of type {ActualTypeName}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlInlineTableSeparatorException.cs b/Tomlet/Exceptions/TomlInlineTableSeparatorException.cs index 0b87dbc..833f37a 100644 --- a/Tomlet/Exceptions/TomlInlineTableSeparatorException.cs +++ b/Tomlet/Exceptions/TomlInlineTableSeparatorException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlInlineTableSeparatorException : TomlExceptionWithLine - { - private readonly char _found; +namespace Tomlet.Exceptions; - public TomlInlineTableSeparatorException(int lineNumber, char found) : base(lineNumber) - { - _found = found; - } +public class TomlInlineTableSeparatorException : TomlExceptionWithLine +{ + private readonly char _found; - public override string Message => $"Expected '}}' or ',' after key-value pair in TOML inline table, found '{_found}'"; + public TomlInlineTableSeparatorException(int lineNumber, char found) : base(lineNumber) + { + _found = found; } + + public override string Message => $"Expected '}}' or ',' after key-value pair in TOML inline table, found '{_found}'"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlInstantiationException.cs b/Tomlet/Exceptions/TomlInstantiationException.cs index ceef77f..6763632 100644 --- a/Tomlet/Exceptions/TomlInstantiationException.cs +++ b/Tomlet/Exceptions/TomlInstantiationException.cs @@ -1,8 +1,7 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TomlInstantiationException : TomlException { - public class TomlInstantiationException : TomlException - { - public override string Message => - "Deserialization of types without a parameterless constructor or a singular parameterized constructor is not supported."; - } + public override string Message => + "Deserialization of types without a parameterless constructor or a singular parameterized constructor is not supported."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlInternalException.cs b/Tomlet/Exceptions/TomlInternalException.cs index 60abb13..950124e 100644 --- a/Tomlet/Exceptions/TomlInternalException.cs +++ b/Tomlet/Exceptions/TomlInternalException.cs @@ -1,13 +1,12 @@ using System; -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TomlInternalException : TomlExceptionWithLine { - public class TomlInternalException : TomlExceptionWithLine + public TomlInternalException(int lineNumber, Exception cause) : base(lineNumber, cause) { - public TomlInternalException(int lineNumber, Exception cause) : base(lineNumber, cause) - { - } - - public override string Message => $"An internal exception occured while parsing line {LineNumber} of the TOML document"; } + + public override string Message => $"An internal exception occured while parsing line {LineNumber} of the TOML document"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlInvalidValueException.cs b/Tomlet/Exceptions/TomlInvalidValueException.cs index 1751657..92d5995 100644 --- a/Tomlet/Exceptions/TomlInvalidValueException.cs +++ b/Tomlet/Exceptions/TomlInvalidValueException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlInvalidValueException : TomlExceptionWithLine - { - private readonly char _found; +namespace Tomlet.Exceptions; - public TomlInvalidValueException(int lineNumber, char found) : base(lineNumber) - { - _found = found; - } +public class TomlInvalidValueException : TomlExceptionWithLine +{ + private readonly char _found; - public override string Message => $"Expected the start of a number, string literal, boolean, array, or table on line {LineNumber}, found '{_found}'"; + public TomlInvalidValueException(int lineNumber, char found) : base(lineNumber) + { + _found = found; } + + public override string Message => $"Expected the start of a number, string literal, boolean, array, or table on line {LineNumber}, found '{_found}'"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlKeyRedefinitionException.cs b/Tomlet/Exceptions/TomlKeyRedefinitionException.cs index 747b08d..0a812eb 100644 --- a/Tomlet/Exceptions/TomlKeyRedefinitionException.cs +++ b/Tomlet/Exceptions/TomlKeyRedefinitionException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlKeyRedefinitionException : TomlExceptionWithLine - { - private readonly string _key; +namespace Tomlet.Exceptions; - public TomlKeyRedefinitionException(int lineNumber, string key) : base(lineNumber) - { - _key = key; - } +public class TomlKeyRedefinitionException : TomlExceptionWithLine +{ + private readonly string _key; - public override string Message => $"TOML document attempts to re-define key '{_key}' on line {LineNumber}"; + public TomlKeyRedefinitionException(int lineNumber, string key) : base(lineNumber) + { + _key = key; } + + public override string Message => $"TOML document attempts to re-define key '{_key}' on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlMissingEqualsException.cs b/Tomlet/Exceptions/TomlMissingEqualsException.cs index 78bd67b..a82a2f6 100644 --- a/Tomlet/Exceptions/TomlMissingEqualsException.cs +++ b/Tomlet/Exceptions/TomlMissingEqualsException.cs @@ -1,13 +1,12 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TomlMissingEqualsException : TomlExceptionWithLine { - public class TomlMissingEqualsException : TomlExceptionWithLine + private readonly char _found; + public TomlMissingEqualsException(int lineNumber, char found) : base(lineNumber) { - private readonly char _found; - public TomlMissingEqualsException(int lineNumber, char found) : base(lineNumber) - { - _found = found; - } - - public override string Message => $"Expecting an equals sign ('=') on line {LineNumber}, but found '{_found}'"; + _found = found; } + + public override string Message => $"Expecting an equals sign ('=') on line {LineNumber}, but found '{_found}'"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlMissingNewlineException.cs b/Tomlet/Exceptions/TomlMissingNewlineException.cs index 872b24a..de129a2 100644 --- a/Tomlet/Exceptions/TomlMissingNewlineException.cs +++ b/Tomlet/Exceptions/TomlMissingNewlineException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlMissingNewlineException : TomlExceptionWithLine - { - private readonly char _found; +namespace Tomlet.Exceptions; - public TomlMissingNewlineException(int lineNumber, char found) : base(lineNumber) - { - _found = found; - } +public class TomlMissingNewlineException : TomlExceptionWithLine +{ + private readonly char _found; - public override string Message => $"Expecting a newline character at the end of a statement on line {LineNumber}, but found an unexpected '{_found}'"; + public TomlMissingNewlineException(int lineNumber, char found) : base(lineNumber) + { + _found = found; } + + public override string Message => $"Expecting a newline character at the end of a statement on line {LineNumber}, but found an unexpected '{_found}'"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlNoSuchValueException.cs b/Tomlet/Exceptions/TomlNoSuchValueException.cs index 35d4ef2..acf3802 100644 --- a/Tomlet/Exceptions/TomlNoSuchValueException.cs +++ b/Tomlet/Exceptions/TomlNoSuchValueException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlNoSuchValueException : TomlException - { - private readonly string _key; +namespace Tomlet.Exceptions; - public TomlNoSuchValueException(string key) - { - _key = key; - } +public class TomlNoSuchValueException : TomlException +{ + private readonly string _key; - public override string Message => $"Attempted to get the value for key {_key} but no value is associated with that key"; + public TomlNoSuchValueException(string key) + { + _key = key; } + + public override string Message => $"Attempted to get the value for key {_key} but no value is associated with that key"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlNonTableArrayUsedAsTableArrayException.cs b/Tomlet/Exceptions/TomlNonTableArrayUsedAsTableArrayException.cs index 3f0df41..810a85d 100644 --- a/Tomlet/Exceptions/TomlNonTableArrayUsedAsTableArrayException.cs +++ b/Tomlet/Exceptions/TomlNonTableArrayUsedAsTableArrayException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlNonTableArrayUsedAsTableArrayException : TomlExceptionWithLine - { - private readonly string _arrayName; +namespace Tomlet.Exceptions; - public TomlNonTableArrayUsedAsTableArrayException(int lineNumber, string arrayName) : base(lineNumber) - { - _arrayName = arrayName; - } +public class TomlNonTableArrayUsedAsTableArrayException : TomlExceptionWithLine +{ + private readonly string _arrayName; - public override string Message => $"{_arrayName} is used as a table-array on line {LineNumber} when it has previously been defined as a static array. This is not allowed."; + public TomlNonTableArrayUsedAsTableArrayException(int lineNumber, string arrayName) : base(lineNumber) + { + _arrayName = arrayName; } + + public override string Message => $"{_arrayName} is used as a table-array on line {LineNumber} when it has previously been defined as a static array. This is not allowed."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlParameterTypeMismatchException.cs b/Tomlet/Exceptions/TomlParameterTypeMismatchException.cs index e9ea62c..fe581aa 100644 --- a/Tomlet/Exceptions/TomlParameterTypeMismatchException.cs +++ b/Tomlet/Exceptions/TomlParameterTypeMismatchException.cs @@ -1,19 +1,18 @@ using System; using System.Reflection; -namespace Tomlet.Exceptions -{ - public class TomlParameterTypeMismatchException : TomlTypeMismatchException - { - private readonly Type _typeBeingInstantiated; - private readonly ParameterInfo _paramBeingDeserialized; +namespace Tomlet.Exceptions; - public TomlParameterTypeMismatchException(Type typeBeingInstantiated, ParameterInfo paramBeingDeserialized, TomlTypeMismatchException cause) : base(cause.ExpectedType, cause.ActualType, paramBeingDeserialized.ParameterType) - { - _typeBeingInstantiated = typeBeingInstantiated; - _paramBeingDeserialized = paramBeingDeserialized; - } +public class TomlParameterTypeMismatchException : TomlTypeMismatchException +{ + private readonly Type _typeBeingInstantiated; + private readonly ParameterInfo _paramBeingDeserialized; - public override string Message => $"While deserializing an object of type {_typeBeingInstantiated}, found parameter {_paramBeingDeserialized.Name} expecting a type of {ExpectedTypeName}, but value in TOML was of type {ActualTypeName}"; + public TomlParameterTypeMismatchException(Type typeBeingInstantiated, ParameterInfo paramBeingDeserialized, TomlTypeMismatchException cause) : base(cause.ExpectedType, cause.ActualType, paramBeingDeserialized.ParameterType) + { + _typeBeingInstantiated = typeBeingInstantiated; + _paramBeingDeserialized = paramBeingDeserialized; } + + public override string Message => $"While deserializing an object of type {_typeBeingInstantiated}, found parameter {_paramBeingDeserialized.Name} expecting a type of {ExpectedTypeName}, but value in TOML was of type {ActualTypeName}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlPrimitiveToDocumentException.cs b/Tomlet/Exceptions/TomlPrimitiveToDocumentException.cs index f9fbfa5..e549426 100644 --- a/Tomlet/Exceptions/TomlPrimitiveToDocumentException.cs +++ b/Tomlet/Exceptions/TomlPrimitiveToDocumentException.cs @@ -1,16 +1,15 @@ using System; -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TomlPrimitiveToDocumentException : TomlException { - public class TomlPrimitiveToDocumentException : TomlException - { - private Type primitiveType; + private Type primitiveType; - public TomlPrimitiveToDocumentException(Type primitiveType) - { - this.primitiveType = primitiveType; - } - - public override string Message => $"Tried to create a TOML document from a primitive value of type {primitiveType.Name}. Documents can only be created from objects."; + public TomlPrimitiveToDocumentException(Type primitiveType) + { + this.primitiveType = primitiveType; } + + public override string Message => $"Tried to create a TOML document from a primitive value of type {primitiveType.Name}. Documents can only be created from objects."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlStringException.cs b/Tomlet/Exceptions/TomlStringException.cs index 2ff4004..eaab10f 100644 --- a/Tomlet/Exceptions/TomlStringException.cs +++ b/Tomlet/Exceptions/TomlStringException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TomlStringException :TomlExceptionWithLine { - public class TomlStringException :TomlExceptionWithLine + public TomlStringException(int lineNumber) : base(lineNumber) { - public TomlStringException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found an invalid TOML string on line {LineNumber}"; } + + public override string Message => $"Found an invalid TOML string on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlTableArrayAlreadyExistsAsNonArrayException.cs b/Tomlet/Exceptions/TomlTableArrayAlreadyExistsAsNonArrayException.cs index d5f7daa..6dc0d34 100644 --- a/Tomlet/Exceptions/TomlTableArrayAlreadyExistsAsNonArrayException.cs +++ b/Tomlet/Exceptions/TomlTableArrayAlreadyExistsAsNonArrayException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlTableArrayAlreadyExistsAsNonArrayException : TomlExceptionWithLine - { - private readonly string _arrayName; +namespace Tomlet.Exceptions; - public TomlTableArrayAlreadyExistsAsNonArrayException(int lineNumber, string arrayName) : base(lineNumber) - { - _arrayName = arrayName; - } +public class TomlTableArrayAlreadyExistsAsNonArrayException : TomlExceptionWithLine +{ + private readonly string _arrayName; - public override string Message => $"{_arrayName} is defined as a table-array (double-bracketed section) on line {LineNumber} but it has previously been used as a non-array type."; + public TomlTableArrayAlreadyExistsAsNonArrayException(int lineNumber, string arrayName) : base(lineNumber) + { + _arrayName = arrayName; } + + public override string Message => $"{_arrayName} is defined as a table-array (double-bracketed section) on line {LineNumber} but it has previously been used as a non-array type."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlTableLockedException.cs b/Tomlet/Exceptions/TomlTableLockedException.cs index ea53381..8aae91c 100644 --- a/Tomlet/Exceptions/TomlTableLockedException.cs +++ b/Tomlet/Exceptions/TomlTableLockedException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlTableLockedException : TomlExceptionWithLine - { - private readonly string _key; +namespace Tomlet.Exceptions; - public TomlTableLockedException(int lineNumber, string key) : base(lineNumber) - { - _key = key; - } +public class TomlTableLockedException : TomlExceptionWithLine +{ + private readonly string _key; - public override string Message => $"TOML table is locked (e.g. defined inline), cannot add or update key {_key} to it on line {LineNumber}"; + public TomlTableLockedException(int lineNumber, string key) : base(lineNumber) + { + _key = key; } + + public override string Message => $"TOML table is locked (e.g. defined inline), cannot add or update key {_key} to it on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlTableRedefinitionException.cs b/Tomlet/Exceptions/TomlTableRedefinitionException.cs index 7799851..5cf1c6f 100644 --- a/Tomlet/Exceptions/TomlTableRedefinitionException.cs +++ b/Tomlet/Exceptions/TomlTableRedefinitionException.cs @@ -1,14 +1,13 @@ -namespace Tomlet.Exceptions -{ - public class TomlTableRedefinitionException : TomlExceptionWithLine - { - private readonly string _key; +namespace Tomlet.Exceptions; - public TomlTableRedefinitionException(int lineNumber, string key) : base(lineNumber) - { - _key = key; - } +public class TomlTableRedefinitionException : TomlExceptionWithLine +{ + private readonly string _key; - public override string Message => $"TOML document attempts to re-define table '{_key}' on line {LineNumber}"; + public TomlTableRedefinitionException(int lineNumber, string key) : base(lineNumber) + { + _key = key; } + + public override string Message => $"TOML document attempts to re-define table '{_key}' on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlTripleQuotedKeyException.cs b/Tomlet/Exceptions/TomlTripleQuotedKeyException.cs index 37f9eee..ed1316f 100644 --- a/Tomlet/Exceptions/TomlTripleQuotedKeyException.cs +++ b/Tomlet/Exceptions/TomlTripleQuotedKeyException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TomlTripleQuotedKeyException : TomlExceptionWithLine { - public class TomlTripleQuotedKeyException : TomlExceptionWithLine + public TomlTripleQuotedKeyException(int lineNumber) : base(lineNumber) { - public TomlTripleQuotedKeyException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found a triple-quoted key on line {LineNumber}. This is not allowed."; } + + public override string Message => $"Found a triple-quoted key on line {LineNumber}. This is not allowed."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TomlTypeMismatchException.cs b/Tomlet/Exceptions/TomlTypeMismatchException.cs index 9c7898f..549f227 100644 --- a/Tomlet/Exceptions/TomlTypeMismatchException.cs +++ b/Tomlet/Exceptions/TomlTypeMismatchException.cs @@ -1,25 +1,24 @@ using System; using Tomlet.Models; -namespace Tomlet.Exceptions -{ - public class TomlTypeMismatchException : TomlException - { - protected readonly string ExpectedTypeName; - protected readonly string ActualTypeName; - protected internal readonly Type ExpectedType; - protected internal readonly Type ActualType; - private readonly Type _context; +namespace Tomlet.Exceptions; - public TomlTypeMismatchException(Type expected, Type actual, Type context) - { - ExpectedTypeName = typeof(TomlValue).IsAssignableFrom(expected) ? expected.Name.Replace("Toml", "") : expected.Name; - ActualTypeName = typeof(TomlValue).IsAssignableFrom(actual) ? actual.Name.Replace("Toml", "") : actual.Name; - ExpectedType = expected; - ActualType = actual; - _context = context; - } +public class TomlTypeMismatchException : TomlException +{ + protected readonly string ExpectedTypeName; + protected readonly string ActualTypeName; + protected internal readonly Type ExpectedType; + protected internal readonly Type ActualType; + private readonly Type _context; - public override string Message => $"While trying to convert to type {_context}, a TOML value of type {ExpectedTypeName} was required but a value of type {ActualTypeName} was found"; + public TomlTypeMismatchException(Type expected, Type actual, Type context) + { + ExpectedTypeName = typeof(TomlValue).IsAssignableFrom(expected) ? expected.Name.Replace("Toml", "") : expected.Name; + ActualTypeName = typeof(TomlValue).IsAssignableFrom(actual) ? actual.Name.Replace("Toml", "") : actual.Name; + ExpectedType = expected; + ActualType = actual; + _context = context; } + + public override string Message => $"While trying to convert to type {_context}, a TOML value of type {ExpectedTypeName} was required but a value of type {ActualTypeName} was found"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TripleQuoteInTomlMultilineLiteralException.cs b/Tomlet/Exceptions/TripleQuoteInTomlMultilineLiteralException.cs index 429af25..924dec7 100644 --- a/Tomlet/Exceptions/TripleQuoteInTomlMultilineLiteralException.cs +++ b/Tomlet/Exceptions/TripleQuoteInTomlMultilineLiteralException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TripleQuoteInTomlMultilineLiteralException : TomlExceptionWithLine { - public class TripleQuoteInTomlMultilineLiteralException : TomlExceptionWithLine + public TripleQuoteInTomlMultilineLiteralException(int lineNumber) : base(lineNumber) { - public TripleQuoteInTomlMultilineLiteralException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found a triple-single-quote (''') inside a multiline string literal on line {LineNumber}. This is not allowed."; } + + public override string Message => $"Found a triple-single-quote (''') inside a multiline string literal on line {LineNumber}. This is not allowed."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/TripleQuoteInTomlMultilineSimpleStringException.cs b/Tomlet/Exceptions/TripleQuoteInTomlMultilineSimpleStringException.cs index ba92384..e5c545f 100644 --- a/Tomlet/Exceptions/TripleQuoteInTomlMultilineSimpleStringException.cs +++ b/Tomlet/Exceptions/TripleQuoteInTomlMultilineSimpleStringException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class TripleQuoteInTomlMultilineSimpleStringException : TomlExceptionWithLine { - public class TripleQuoteInTomlMultilineSimpleStringException : TomlExceptionWithLine + public TripleQuoteInTomlMultilineSimpleStringException(int lineNumber) : base(lineNumber) { - public TripleQuoteInTomlMultilineSimpleStringException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found a triple-double-quote (\"\"\") inside a multiline simple string on line {LineNumber}. This is not allowed."; } + + public override string Message => $"Found a triple-double-quote (\"\"\") inside a multiline simple string on line {LineNumber}. This is not allowed."; } \ No newline at end of file diff --git a/Tomlet/Exceptions/UnterminatedTomlKeyException.cs b/Tomlet/Exceptions/UnterminatedTomlKeyException.cs index 7ca706a..a3f38ff 100644 --- a/Tomlet/Exceptions/UnterminatedTomlKeyException.cs +++ b/Tomlet/Exceptions/UnterminatedTomlKeyException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class UnterminatedTomlKeyException : TomlExceptionWithLine { - public class UnterminatedTomlKeyException : TomlExceptionWithLine + public UnterminatedTomlKeyException(int lineNumber) : base(lineNumber) { - public UnterminatedTomlKeyException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found an unterminated quoted key on line {LineNumber}"; } + + public override string Message => $"Found an unterminated quoted key on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/UnterminatedTomlStringException.cs b/Tomlet/Exceptions/UnterminatedTomlStringException.cs index 2bb7998..63a2340 100644 --- a/Tomlet/Exceptions/UnterminatedTomlStringException.cs +++ b/Tomlet/Exceptions/UnterminatedTomlStringException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class UnterminatedTomlStringException : TomlExceptionWithLine { - public class UnterminatedTomlStringException : TomlExceptionWithLine + public UnterminatedTomlStringException(int lineNumber) : base(lineNumber) { - public UnterminatedTomlStringException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found an unterminated TOML string on line {LineNumber}"; } + + public override string Message => $"Found an unterminated TOML string on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/UnterminatedTomlTableArrayException.cs b/Tomlet/Exceptions/UnterminatedTomlTableArrayException.cs index e16d1cb..cca1912 100644 --- a/Tomlet/Exceptions/UnterminatedTomlTableArrayException.cs +++ b/Tomlet/Exceptions/UnterminatedTomlTableArrayException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class UnterminatedTomlTableArrayException : TomlExceptionWithLine { - public class UnterminatedTomlTableArrayException : TomlExceptionWithLine + public UnterminatedTomlTableArrayException(int lineNumber) : base(lineNumber) { - public UnterminatedTomlTableArrayException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found an unterminated table-array (expecting two ]s to close it) on line {LineNumber}"; } + + public override string Message => $"Found an unterminated table-array (expecting two ]s to close it) on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Exceptions/UnterminatedTomlTableNameException.cs b/Tomlet/Exceptions/UnterminatedTomlTableNameException.cs index a8a12fa..5e43544 100644 --- a/Tomlet/Exceptions/UnterminatedTomlTableNameException.cs +++ b/Tomlet/Exceptions/UnterminatedTomlTableNameException.cs @@ -1,11 +1,10 @@ -namespace Tomlet.Exceptions +namespace Tomlet.Exceptions; + +public class UnterminatedTomlTableNameException : TomlExceptionWithLine { - public class UnterminatedTomlTableNameException : TomlExceptionWithLine + public UnterminatedTomlTableNameException(int lineNumber) : base(lineNumber) { - public UnterminatedTomlTableNameException(int lineNumber) : base(lineNumber) - { - } - - public override string Message => $"Found an unterminated table name on line {LineNumber}"; } + + public override string Message => $"Found an unterminated table name on line {LineNumber}"; } \ No newline at end of file diff --git a/Tomlet/Extensions/GenericExtensions.cs b/Tomlet/Extensions/GenericExtensions.cs index e56ac53..363ce76 100644 --- a/Tomlet/Extensions/GenericExtensions.cs +++ b/Tomlet/Extensions/GenericExtensions.cs @@ -6,166 +6,165 @@ using System.Text; using Tomlet.Exceptions; -namespace Tomlet.Extensions +namespace Tomlet.Extensions; + +internal static class GenericExtensions { - internal static class GenericExtensions + private static readonly HashSet IllegalChars = new() { - private static readonly HashSet IllegalChars = new() - { - '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', - '\u0008', '\u000b', '\u000e', '\u000f', '\u0010', '\u0011', '\u0012', '\u0013', - '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', - '\u001c', '\u001d', '\u001e', '\u001f', '\u007f' - }; + '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', + '\u0008', '\u000b', '\u000e', '\u000f', '\u0010', '\u0011', '\u0012', '\u0013', + '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', + '\u001c', '\u001d', '\u001e', '\u001f', '\u007f' + }; - internal static bool IsWhitespace(this int val) => !val.IsNewline() && char.IsWhiteSpace((char)val); + internal static bool IsWhitespace(this int val) => !val.IsNewline() && char.IsWhiteSpace((char)val); - internal static bool IsEquals(this int val) => val == '='; + internal static bool IsEquals(this int val) => val == '='; - internal static bool IsSingleQuote(this int val) => val == '\''; + internal static bool IsSingleQuote(this int val) => val == '\''; - internal static bool IsDoubleQuote(this int val) => val == '"'; + internal static bool IsDoubleQuote(this int val) => val == '"'; - internal static bool IsHashSign(this int val) => val == '#'; + internal static bool IsHashSign(this int val) => val == '#'; - internal static bool IsNewline(this int val) => val is '\r' or '\n'; + internal static bool IsNewline(this int val) => val is '\r' or '\n'; - internal static bool IsDigit(this int val) => char.IsDigit((char)val); + internal static bool IsDigit(this int val) => char.IsDigit((char)val); - internal static bool IsComma(this int val) => val == ','; + internal static bool IsComma(this int val) => val == ','; - internal static bool IsPeriod(this int val) => val == '.'; + internal static bool IsPeriod(this int val) => val == '.'; - internal static bool IsEndOfArrayChar(this int val) => val == ']'; + internal static bool IsEndOfArrayChar(this int val) => val == ']'; - internal static bool IsEndOfInlineObjectChar(this int val) => val == '}'; + internal static bool IsEndOfInlineObjectChar(this int val) => val == '}'; - private static bool IsPermittedInNumberLiteral(this char val) - => val is '_' or '+' or '-' or 'e' or (>= '0' and <= '9'); + private static bool IsPermittedInNumberLiteral(this char val) + => val is '_' or '+' or '-' or 'e' or (>= '0' and <= '9'); - internal static bool IsPermittedInIntegerLiteral(this char val) - => val is >= 'a' and <= 'f' || val.IsPermittedInNumberLiteral(); + internal static bool IsPermittedInIntegerLiteral(this char val) + => val is >= 'a' and <= 'f' || val.IsPermittedInNumberLiteral(); - internal static bool IsPermittedInFloatLiteral(this char val) - => val is '.' || val.IsPermittedInNumberLiteral(); + internal static bool IsPermittedInFloatLiteral(this char val) + => val is '.' || val.IsPermittedInNumberLiteral(); - internal static bool IsHexDigit(this char c) - { - var val = (int)c; + internal static bool IsHexDigit(this char c) + { + var val = (int)c; - if (val.IsDigit()) - return true; + if (val.IsDigit()) + return true; - var upper = char.ToUpperInvariant((char)val); - return upper is >= 'A' and <= 'F'; - } + var upper = char.ToUpperInvariant((char)val); + return upper is >= 'A' and <= 'F'; + } - internal static bool TryPeek(this TomletStringReader reader, out int nextChar) - { - nextChar = reader.Peek(); - return nextChar != -1; - } + internal static bool TryPeek(this TomletStringReader reader, out int nextChar) + { + nextChar = reader.Peek(); + return nextChar != -1; + } - internal static int SkipWhitespace(this TomletStringReader reader) => reader.ReadWhile(c => c.IsWhitespace()).Length; + internal static int SkipWhitespace(this TomletStringReader reader) => reader.ReadWhile(c => c.IsWhitespace()).Length; - internal static void SkipPotentialCarriageReturn(this TomletStringReader reader) - { - if (reader.TryPeek(out var nextChar) && nextChar == '\r') - reader.Read(); - } + internal static void SkipPotentialCarriageReturn(this TomletStringReader reader) + { + if (reader.TryPeek(out var nextChar) && nextChar == '\r') + reader.Read(); + } - internal static void SkipAnyComment(this TomletStringReader reader) - { - //Skip anything up until the \r or \n if we start with a hash. - if (reader.TryPeek(out var maybeHash) && maybeHash.IsHashSign()) - reader.ReadWhile(commentChar => !commentChar.IsNewline()); - } + internal static void SkipAnyComment(this TomletStringReader reader) + { + //Skip anything up until the \r or \n if we start with a hash. + if (reader.TryPeek(out var maybeHash) && maybeHash.IsHashSign()) + reader.ReadWhile(commentChar => !commentChar.IsNewline()); + } - internal static int SkipAnyNewlineOrWhitespace(this TomletStringReader reader) - { - return reader.ReadWhile(c => c.IsNewline() || c.IsWhitespace()).Count(c => c == '\n'); - } + internal static int SkipAnyNewlineOrWhitespace(this TomletStringReader reader) + { + return reader.ReadWhile(c => c.IsNewline() || c.IsWhitespace()).Count(c => c == '\n'); + } - internal static int SkipAnyCommentNewlineWhitespaceEtc(this TomletStringReader reader) + internal static int SkipAnyCommentNewlineWhitespaceEtc(this TomletStringReader reader) + { + var countRead = 0; + while (reader.TryPeek(out var nextChar)) { - var countRead = 0; - while (reader.TryPeek(out var nextChar)) - { - if (!nextChar.IsHashSign() && !nextChar.IsNewline() && !nextChar.IsWhitespace()) - break; - - if (nextChar.IsHashSign()) - reader.SkipAnyComment(); - countRead += reader.SkipAnyNewlineOrWhitespace(); - } - - return countRead; + if (!nextChar.IsHashSign() && !nextChar.IsNewline() && !nextChar.IsWhitespace()) + break; + + if (nextChar.IsHashSign()) + reader.SkipAnyComment(); + countRead += reader.SkipAnyNewlineOrWhitespace(); } - internal static int SkipAnyNewline(this TomletStringReader reader) => reader.ReadWhile(c => c.IsNewline()).Count(c => c == '\n'); + return countRead; + } - internal static char[] ReadChars(this TomletStringReader reader, int count) - { - char[] result = new char[count]; - reader.ReadBlock(result, 0, count); + internal static int SkipAnyNewline(this TomletStringReader reader) => reader.ReadWhile(c => c.IsNewline()).Count(c => c == '\n'); - return result; - } + internal static char[] ReadChars(this TomletStringReader reader, int count) + { + char[] result = new char[count]; + reader.ReadBlock(result, 0, count); - internal static string ReadWhile(this TomletStringReader reader, Predicate predicate) - { - var ret = new StringBuilder(); - //Read up until whitespace or an equals - while (reader.TryPeek(out var nextChar) && predicate(nextChar)) - { - ret.Append((char)reader.Read()); - } - - return ret.ToString(); - } + return result; + } - internal static bool ExpectAndConsume(this TomletStringReader reader, char expectWhat) + internal static string ReadWhile(this TomletStringReader reader, Predicate predicate) + { + var ret = new StringBuilder(); + //Read up until whitespace or an equals + while (reader.TryPeek(out var nextChar) && predicate(nextChar)) { - if (!reader.TryPeek(out var nextChar)) - return false; + ret.Append((char)reader.Read()); + } - if (nextChar == expectWhat) - { - reader.Read(); - return true; - } + return ret.ToString(); + } + internal static bool ExpectAndConsume(this TomletStringReader reader, char expectWhat) + { + if (!reader.TryPeek(out var nextChar)) return false; - } -#if !NET5_0_OR_GREATER - public static void Deconstruct(this KeyValuePair pair, out TKey one, out TValue two) + if (nextChar == expectWhat) { - one = pair.Key; - two = pair.Value; + reader.Read(); + return true; } + + return false; + } + +#if !NET5_0_OR_GREATER + public static void Deconstruct(this KeyValuePair pair, out TKey one, out TValue two) + { + one = pair.Key; + two = pair.Value; + } #endif - public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrEmpty(s) || string.IsNullOrEmpty(s.Trim()); + public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrEmpty(s) || string.IsNullOrEmpty(s.Trim()); - internal static T? GetCustomAttribute(this MemberInfo info) where T : Attribute => info.GetCustomAttributes(false).Where(a => a is T).Cast().FirstOrDefault(); + internal static T? GetCustomAttribute(this MemberInfo info) where T : Attribute => info.GetCustomAttributes(false).Where(a => a is T).Cast().FirstOrDefault(); - internal static void EnsureLegalChar(this int c, int currentLineNum) - { - if (IllegalChars.Contains(c)) - throw new TomlUnescapedUnicodeControlCharException(currentLineNum, c); - } + internal static void EnsureLegalChar(this int c, int currentLineNum) + { + if (IllegalChars.Contains(c)) + throw new TomlUnescapedUnicodeControlCharException(currentLineNum, c); + } - /// - /// Shim to properly support Contains(char) on net3.5. We prefer using the char version because it's hardware-accelerated on modern .NET. - /// - [MethodImpl((MethodImplOptions)0x0100)] // AggressiveInlining in constant form because net3.5 doesn't define it + /// + /// Shim to properly support Contains(char) on net3.5. We prefer using the char version because it's hardware-accelerated on modern .NET. + /// + [MethodImpl((MethodImplOptions)0x0100)] // AggressiveInlining in constant form because net3.5 doesn't define it #if NET6_0 || NETSTANDARD2_0 - public static bool RuntimeCorrectContains(this string original, char c) - => original.Contains(c); + public static bool RuntimeCorrectContains(this string original, char c) + => original.Contains(c); #else public static bool RuntimeCorrectContains(this string original, char c) => original.Contains(c.ToString()); #endif - } } \ No newline at end of file diff --git a/Tomlet/Extensions/ReflectionExtensions.cs b/Tomlet/Extensions/ReflectionExtensions.cs index ec316d9..436bdee 100644 --- a/Tomlet/Extensions/ReflectionExtensions.cs +++ b/Tomlet/Extensions/ReflectionExtensions.cs @@ -1,53 +1,57 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; -namespace Tomlet.Extensions +namespace Tomlet.Extensions; + +internal static class ReflectionExtensions { - internal static class ReflectionExtensions - { +#if MODERN_DOTNET + internal static bool TryGetBestMatchConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type, out ConstructorInfo? bestMatchConstructor) +#else internal static bool TryGetBestMatchConstructor(this Type type, out ConstructorInfo? bestMatchConstructor) +#endif + { + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); + if (constructors.Length == 0) { - var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); - if (constructors.Length == 0) - { - bestMatchConstructor = null; - return false; - } - - var parameterlessConstructor = constructors.FirstOrDefault(c => c.GetParameters().Length == 0); - if (parameterlessConstructor != null) - { - bestMatchConstructor = parameterlessConstructor; - return true; - } - - var parameterizedConstructors = constructors.Where(c => c.GetParameters().Length > 0).ToArray(); - if (parameterizedConstructors.Length > 1) - { - bestMatchConstructor = null; - return false; - } + bestMatchConstructor = null; + return false; + } - bestMatchConstructor = parameterizedConstructors.Single(); + var parameterlessConstructor = constructors.FirstOrDefault(c => c.GetParameters().Length == 0); + if (parameterlessConstructor != null) + { + bestMatchConstructor = parameterlessConstructor; return true; } - internal static bool IsIntegerType(this Type type) { - switch (Type.GetTypeCode(type)) { - case TypeCode.Byte: - case TypeCode.SByte: - case TypeCode.UInt16: - case TypeCode.Int16: - case TypeCode.UInt32: - case TypeCode.Int32: - case TypeCode.UInt64: - case TypeCode.Int64: - return true; - default: - return false; - } + var parameterizedConstructors = constructors.Where(c => c.GetParameters().Length > 0).ToArray(); + if (parameterizedConstructors.Length > 1) + { + bestMatchConstructor = null; + return false; } + bestMatchConstructor = parameterizedConstructors.Single(); + return true; + } + + internal static bool IsIntegerType(this Type type) { + switch (Type.GetTypeCode(type)) { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.UInt16: + case TypeCode.Int16: + case TypeCode.UInt32: + case TypeCode.Int32: + case TypeCode.UInt64: + case TypeCode.Int64: + return true; + default: + return false; + } } + } \ No newline at end of file diff --git a/Tomlet/Extensions/StringExtensions.cs b/Tomlet/Extensions/StringExtensions.cs index 0c174d1..8dafc37 100644 --- a/Tomlet/Extensions/StringExtensions.cs +++ b/Tomlet/Extensions/StringExtensions.cs @@ -1,24 +1,23 @@ using System.Text; -namespace Tomlet.Extensions +namespace Tomlet.Extensions; + +internal static class StringExtensions { - internal static class StringExtensions + internal static string ToPascalCase(this string str) { - internal static string ToPascalCase(this string str) - { - var sb = new StringBuilder(str.Length); + var sb = new StringBuilder(str.Length); - if (str.Length > 0) - { - sb.Append(char.ToUpper(str[0])); - } - - for (var i = 1; i < str.Length; i++) - { - sb.Append(char.IsWhiteSpace(str[i - 1]) ? char.ToUpper(str[i]) : str[i]); - } + if (str.Length > 0) + { + sb.Append(char.ToUpper(str[0])); + } - return sb.ToString(); + for (var i = 1; i < str.Length; i++) + { + sb.Append(char.IsWhiteSpace(str[i - 1]) ? char.ToUpper(str[i]) : str[i]); } + + return sb.ToString(); } } \ No newline at end of file diff --git a/Tomlet/Models/TomlArray.cs b/Tomlet/Models/TomlArray.cs index db5ec4a..75c7fbc 100644 --- a/Tomlet/Models/TomlArray.cs +++ b/Tomlet/Models/TomlArray.cs @@ -1,138 +1,142 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; -namespace Tomlet.Models +namespace Tomlet.Models; + +public class TomlArray : TomlValue, IEnumerable { - public class TomlArray : TomlValue, IEnumerable + public readonly List ArrayValues = new(); + internal bool IsLockedToBeTableArray; + public override string StringValue => $"Toml Array ({ArrayValues.Count} values)"; + +#if MODERN_DOTNET + public void Add<[DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] T>(T t) where T: new() +#else + public void Add(T t) where T: new() +#endif { - public readonly List ArrayValues = new(); - internal bool IsLockedToBeTableArray; - public override string StringValue => $"Toml Array ({ArrayValues.Count} values)"; - - public void Add(T t) where T: new() - { - var tomlValue = t is TomlValue tv ? tv : TomletMain.ValueFrom(t); - if(tomlValue != null) - ArrayValues.Add(tomlValue); - } + var tomlValue = t is TomlValue tv ? tv : TomletMain.ValueFrom(t); + if(tomlValue != null) + ArrayValues.Add(tomlValue); + } - public bool IsTableArray => IsLockedToBeTableArray || ArrayValues.All(t => t is TomlTable); + public bool IsTableArray => IsLockedToBeTableArray || ArrayValues.All(t => t is TomlTable); - public bool CanBeSerializedInline => !IsTableArray || //Simple array - ArrayValues.All(o => o is TomlTable { ShouldBeSerializedInline: true }) && ArrayValues.Count <= 5; //Table array of inline tables, 5 or fewer of them. + public bool CanBeSerializedInline => !IsTableArray || //Simple array + ArrayValues.All(o => o is TomlTable { ShouldBeSerializedInline: true }) && ArrayValues.Count <= 5; //Table array of inline tables, 5 or fewer of them. - /// - /// Returns true if this is not a table-array, there are not any sub-arrays or tables, and none of the entries contain comments. - /// - public bool IsSimpleArray => !IsLockedToBeTableArray && !ArrayValues.Any(o => o is TomlArray or TomlTable || !o.Comments.ThereAreNoComments); + /// + /// Returns true if this is not a table-array, there are not any sub-arrays or tables, and none of the entries contain comments. + /// + public bool IsSimpleArray => !IsLockedToBeTableArray && !ArrayValues.Any(o => o is TomlArray or TomlTable || !o.Comments.ThereAreNoComments); - // ReSharper disable once UnusedMember.Global - public TomlValue this[int index] => ArrayValues[index]; + // ReSharper disable once UnusedMember.Global + public TomlValue this[int index] => ArrayValues[index]; - public int Count => ArrayValues.Count; + public int Count => ArrayValues.Count; - public override string SerializedValue => SerializeInline(!IsSimpleArray); //If non-simple, put newlines after commas. + public override string SerializedValue => SerializeInline(!IsSimpleArray); //If non-simple, put newlines after commas. - public string SerializeInline(bool multiline) - { - if (!CanBeSerializedInline) - throw new Exception("Complex Toml Tables cannot be serialized into a TomlArray if the TomlArray is not a Table Array. " + - "This means that the TOML array cannot contain anything other than tables. If you are manually accessing SerializedValue on the TomlArray, you should probably be calling SerializeTableArray here. " + - "(Check the CanBeSerializedInline property and call that method if it is false)"); + public string SerializeInline(bool multiline) + { + if (!CanBeSerializedInline) + throw new Exception("Complex Toml Tables cannot be serialized into a TomlArray if the TomlArray is not a Table Array. " + + "This means that the TOML array cannot contain anything other than tables. If you are manually accessing SerializedValue on the TomlArray, you should probably be calling SerializeTableArray here. " + + "(Check the CanBeSerializedInline property and call that method if it is false)"); - var builder = new StringBuilder("["); + var builder = new StringBuilder("["); - var sep = multiline ? '\n' : ' '; + var sep = multiline ? '\n' : ' '; - foreach (var value in this) - { - builder.Append(sep); + foreach (var value in this) + { + builder.Append(sep); - if (value.Comments.PrecedingComment != null) - { - builder.Append(value.Comments.FormatPrecedingComment(1)) - .Append('\n'); - } + if (value.Comments.PrecedingComment != null) + { + builder.Append(value.Comments.FormatPrecedingComment(1)) + .Append('\n'); + } - if(multiline) - builder.Append('\t'); + if(multiline) + builder.Append('\t'); - builder.Append(value.SerializedValue); + builder.Append(value.SerializedValue); - builder.Append(','); + builder.Append(','); - if (value.Comments.InlineComment != null) - { - builder.Append(" # ") - .Append(value.Comments.InlineComment); - } + if (value.Comments.InlineComment != null) + { + builder.Append(" # ") + .Append(value.Comments.InlineComment); } + } - builder.Append(sep); + builder.Append(sep); - builder.Append(']'); + builder.Append(']'); - return builder.ToString(); - } + return builder.ToString(); + } - public string SerializeTableArray(string key) - { - if (!IsTableArray) - throw new Exception("Cannot serialize normal arrays using this method. Use the normal TomlValue.SerializedValue property."); + public string SerializeTableArray(string key) + { + if (!IsTableArray) + throw new Exception("Cannot serialize normal arrays using this method. Use the normal TomlValue.SerializedValue property."); - var builder = new StringBuilder(); + var builder = new StringBuilder(); - if (Comments.InlineComment != null) - throw new Exception("Sorry, but inline comments aren't supported on table-arrays themselves. See https://github.com/SamboyCoding/Tomlet/blob/master/Docs/InlineCommentsOnTableArrays.md for my rationale on this."); + if (Comments.InlineComment != null) + throw new Exception("Sorry, but inline comments aren't supported on table-arrays themselves. See https://github.com/SamboyCoding/Tomlet/blob/master/Docs/InlineCommentsOnTableArrays.md for my rationale on this."); - var first = true; - foreach (var value in this) + var first = true; + foreach (var value in this) + { + if (value is not TomlTable table) + throw new Exception($"Toml Table-Array contains non-table entry? Value is {value}"); + + if (value.Comments.PrecedingComment != null) { - if (value is not TomlTable table) - throw new Exception($"Toml Table-Array contains non-table entry? Value is {value}"); - - if (value.Comments.PrecedingComment != null) - { - if (first && Comments.PrecedingComment != null) - //if we have a preceding comment on the array itself, we add a blank line - //prior to the preceding comment on the first table. - builder.Append('\n'); + if (first && Comments.PrecedingComment != null) + //if we have a preceding comment on the array itself, we add a blank line + //prior to the preceding comment on the first table. + builder.Append('\n'); - builder.Append(value.Comments.FormatPrecedingComment()) - .Append('\n'); - } - - first = false; + builder.Append(value.Comments.FormatPrecedingComment()) + .Append('\n'); + } - //Write table-array header - builder.Append("[[").Append(key).Append("]]"); + first = false; - if (value.Comments.InlineComment != null) - //Append inline comment on the table itself to the table-array header. - builder.Append(" # ").Append(value.Comments.InlineComment); + //Write table-array header + builder.Append("[[").Append(key).Append("]]"); - //Blank line before the table body itself - builder.Append('\n'); + if (value.Comments.InlineComment != null) + //Append inline comment on the table itself to the table-array header. + builder.Append(" # ").Append(value.Comments.InlineComment); - //Write table body without header (as we just wrote that), then a blank line. - builder.Append(table.SerializeNonInlineTable(key, false)) - .Append('\n'); - } + //Blank line before the table body itself + builder.Append('\n'); - return builder.ToString(); + //Write table body without header (as we just wrote that), then a blank line. + builder.Append(table.SerializeNonInlineTable(key, false)) + .Append('\n'); } - public IEnumerator GetEnumerator() - { - return ArrayValues.GetEnumerator(); - } + return builder.ToString(); + } - IEnumerator IEnumerable.GetEnumerator() - { - return ArrayValues.GetEnumerator(); - } + public IEnumerator GetEnumerator() + { + return ArrayValues.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ArrayValues.GetEnumerator(); } -} +} \ No newline at end of file diff --git a/Tomlet/Models/TomlBoolean.cs b/Tomlet/Models/TomlBoolean.cs index 3232ed7..58850b4 100644 --- a/Tomlet/Models/TomlBoolean.cs +++ b/Tomlet/Models/TomlBoolean.cs @@ -1,22 +1,21 @@ -namespace Tomlet.Models +namespace Tomlet.Models; + +public class TomlBoolean : TomlValue { - public class TomlBoolean : TomlValue - { - public static TomlBoolean True => new(true); - public static TomlBoolean False => new(false); + public static TomlBoolean True => new(true); + public static TomlBoolean False => new(false); - private bool _value; - private TomlBoolean(bool value) - { - _value = value; - } + private bool _value; + private TomlBoolean(bool value) + { + _value = value; + } - public static TomlBoolean ValueOf(bool b) => b ? True : False; + public static TomlBoolean ValueOf(bool b) => b ? True : False; - public bool Value => _value; + public bool Value => _value; - public override string StringValue => Value ? bool.TrueString.ToLowerInvariant() : bool.FalseString.ToLowerInvariant(); + public override string StringValue => Value ? bool.TrueString.ToLowerInvariant() : bool.FalseString.ToLowerInvariant(); - public override string SerializedValue => StringValue; - } + public override string SerializedValue => StringValue; } \ No newline at end of file diff --git a/Tomlet/Models/TomlDocument.cs b/Tomlet/Models/TomlDocument.cs index cdf8118..44b8fca 100644 --- a/Tomlet/Models/TomlDocument.cs +++ b/Tomlet/Models/TomlDocument.cs @@ -1,44 +1,43 @@ using System.Text; -namespace Tomlet.Models +namespace Tomlet.Models; + +public class TomlDocument : TomlTable { - public class TomlDocument : TomlTable - { - // ReSharper disable once UnusedMember.Global - public static TomlDocument CreateEmpty() => new(); + // ReSharper disable once UnusedMember.Global + public static TomlDocument CreateEmpty() => new(); - public string? TrailingComment { get; set; } + public string? TrailingComment { get; set; } - internal TomlDocument() - { - //Non-public ctor. - } + internal TomlDocument() + { + //Non-public ctor. + } - internal TomlDocument(TomlTable from) + internal TomlDocument(TomlTable from) + { + foreach (var key in from.Keys) { - foreach (var key in from.Keys) - { - PutValue(key, from.GetValue(key)); - } + PutValue(key, from.GetValue(key)); } + } - private string SerializeDocument() - { - var sb = new StringBuilder(); - sb.Append(SerializeNonInlineTable(null, false)); - - if (TrailingComment != null) - { - var comment = new TomlCommentData {PrecedingComment = TrailingComment}; - sb.Append('\n'); - sb.Append(comment.FormatPrecedingComment()); - } + private string SerializeDocument() + { + var sb = new StringBuilder(); + sb.Append(SerializeNonInlineTable(null, false)); - return sb.ToString(); + if (TrailingComment != null) + { + var comment = new TomlCommentData {PrecedingComment = TrailingComment}; + sb.Append('\n'); + sb.Append(comment.FormatPrecedingComment()); } - public override string SerializedValue => SerializeDocument(); - - public override string StringValue => $"Toml root document ({Entries.Count} entries)"; + return sb.ToString(); } + + public override string SerializedValue => SerializeDocument(); + + public override string StringValue => $"Toml root document ({Entries.Count} entries)"; } \ No newline at end of file diff --git a/Tomlet/Models/TomlDouble.cs b/Tomlet/Models/TomlDouble.cs index b7a0124..57292e2 100644 --- a/Tomlet/Models/TomlDouble.cs +++ b/Tomlet/Models/TomlDouble.cs @@ -1,40 +1,39 @@ using System.Globalization; -namespace Tomlet.Models -{ - public class TomlDouble : TomlValue - { - private double _value; +namespace Tomlet.Models; - public TomlDouble(double value) - { - _value = value; - } +public class TomlDouble : TomlValue +{ + private double _value; - internal static TomlDouble? Parse(string valueInToml) - { - var nullableDouble = TomlNumberUtils.GetDoubleValue(valueInToml); + public TomlDouble(double value) + { + _value = value; + } - if (!nullableDouble.HasValue) - return null; + internal static TomlDouble? Parse(string valueInToml) + { + var nullableDouble = TomlNumberUtils.GetDoubleValue(valueInToml); - return new TomlDouble(nullableDouble.Value); - } + if (!nullableDouble.HasValue) + return null; - public bool HasDecimal => Value != (int) Value; - public double Value => _value; + return new TomlDouble(nullableDouble.Value); + } - public bool IsNaN => double.IsNaN(Value); - public bool IsInfinity => double.IsInfinity(Value); + public bool HasDecimal => Value != (int) Value; + public double Value => _value; - public override string StringValue => this switch - { - {IsInfinity: true} => double.IsPositiveInfinity(Value) ? "inf" : "-inf", - {IsNaN: true} => "nan", - {HasDecimal: true} => Value.ToString(CultureInfo.InvariantCulture), - _ => Value.ToString("F1", CultureInfo.InvariantCulture) //When no decimal, force a decimal point (.0) to force any consuming tools (including ourselves!) to re-parse as float. - }; + public bool IsNaN => double.IsNaN(Value); + public bool IsInfinity => double.IsInfinity(Value); - public override string SerializedValue => StringValue; - } -} + public override string StringValue => this switch + { + {IsInfinity: true} => double.IsPositiveInfinity(Value) ? "inf" : "-inf", + {IsNaN: true} => "nan", + {HasDecimal: true} => Value.ToString(CultureInfo.InvariantCulture), + _ => Value.ToString("F1", CultureInfo.InvariantCulture) //When no decimal, force a decimal point (.0) to force any consuming tools (including ourselves!) to re-parse as float. + }; + + public override string SerializedValue => StringValue; +} \ No newline at end of file diff --git a/Tomlet/Models/TomlLocalDate.cs b/Tomlet/Models/TomlLocalDate.cs index 5d97fa7..61ab624 100644 --- a/Tomlet/Models/TomlLocalDate.cs +++ b/Tomlet/Models/TomlLocalDate.cs @@ -1,29 +1,28 @@ using System; using System.Xml; -namespace Tomlet.Models +namespace Tomlet.Models; + +public class TomlLocalDate : TomlValue, ITomlValueWithDateTime { - public class TomlLocalDate : TomlValue, ITomlValueWithDateTime - { - private readonly DateTime _value; + private readonly DateTime _value; - public TomlLocalDate(DateTime value) - { - _value = value; - } + public TomlLocalDate(DateTime value) + { + _value = value; + } - public DateTime Value => _value; + public DateTime Value => _value; - public override string StringValue => XmlConvert.ToString(Value, XmlDateTimeSerializationMode.Unspecified); //XmlConvert specifies RFC 3339 + public override string StringValue => XmlConvert.ToString(Value, XmlDateTimeSerializationMode.Unspecified); //XmlConvert specifies RFC 3339 - public static TomlLocalDate? Parse(string input) - { - if (!DateTime.TryParse(input, out var dt)) - return null; + public static TomlLocalDate? Parse(string input) + { + if (!DateTime.TryParse(input, out var dt)) + return null; - return new TomlLocalDate(dt); - } - - public override string SerializedValue => StringValue; + return new TomlLocalDate(dt); } + + public override string SerializedValue => StringValue; } \ No newline at end of file diff --git a/Tomlet/Models/TomlLocalDateTime.cs b/Tomlet/Models/TomlLocalDateTime.cs index a3c1268..2c0d359 100644 --- a/Tomlet/Models/TomlLocalDateTime.cs +++ b/Tomlet/Models/TomlLocalDateTime.cs @@ -2,29 +2,28 @@ using System; using System.Xml; -namespace Tomlet.Models +namespace Tomlet.Models; + +public class TomlLocalDateTime : TomlValue, ITomlValueWithDateTime { - public class TomlLocalDateTime : TomlValue, ITomlValueWithDateTime - { - private readonly DateTime _value; + private readonly DateTime _value; - public TomlLocalDateTime(DateTime value) - { - _value = value; - } + public TomlLocalDateTime(DateTime value) + { + _value = value; + } - public DateTime Value => _value; + public DateTime Value => _value; - public override string StringValue => XmlConvert.ToString(Value, XmlDateTimeSerializationMode.Unspecified); //XmlConvert specifies RFC 3339 + public override string StringValue => XmlConvert.ToString(Value, XmlDateTimeSerializationMode.Unspecified); //XmlConvert specifies RFC 3339 - public static TomlLocalDateTime? Parse(string input) - { - if (!DateTime.TryParse(input, out var dt)) - return null; + public static TomlLocalDateTime? Parse(string input) + { + if (!DateTime.TryParse(input, out var dt)) + return null; - return new TomlLocalDateTime(dt); - } - - public override string SerializedValue => StringValue; + return new TomlLocalDateTime(dt); } + + public override string SerializedValue => StringValue; } \ No newline at end of file diff --git a/Tomlet/Models/TomlLocalTime.cs b/Tomlet/Models/TomlLocalTime.cs index f05de7b..3f4e414 100644 --- a/Tomlet/Models/TomlLocalTime.cs +++ b/Tomlet/Models/TomlLocalTime.cs @@ -1,28 +1,27 @@ using System; -namespace Tomlet.Models +namespace Tomlet.Models; + +public class TomlLocalTime : TomlValue { - public class TomlLocalTime : TomlValue - { - private readonly TimeSpan _value; + private readonly TimeSpan _value; - public TomlLocalTime(TimeSpan value) - { - _value = value; - } + public TomlLocalTime(TimeSpan value) + { + _value = value; + } - public TimeSpan Value => _value; + public TimeSpan Value => _value; - public override string StringValue => Value.ToString(); + public override string StringValue => Value.ToString(); - public static TomlLocalTime? Parse(string input) - { - if (!TimeSpan.TryParse(input, out var dt)) - return null; + public static TomlLocalTime? Parse(string input) + { + if (!TimeSpan.TryParse(input, out var dt)) + return null; - return new TomlLocalTime(dt); - } - - public override string SerializedValue => StringValue; + return new TomlLocalTime(dt); } + + public override string SerializedValue => StringValue; } \ No newline at end of file diff --git a/Tomlet/Models/TomlLong.cs b/Tomlet/Models/TomlLong.cs index 681ea8e..06e1ab6 100644 --- a/Tomlet/Models/TomlLong.cs +++ b/Tomlet/Models/TomlLong.cs @@ -1,27 +1,26 @@ -namespace Tomlet.Models +namespace Tomlet.Models; + +public class TomlLong : TomlValue { - public class TomlLong : TomlValue - { - private long _value; + private long _value; - public TomlLong(long value) - { - _value = value; - } + public TomlLong(long value) + { + _value = value; + } - internal static TomlLong? Parse(string valueInToml) - { - var nullableDouble = TomlNumberUtils.GetLongValue(valueInToml); + internal static TomlLong? Parse(string valueInToml) + { + var nullableDouble = TomlNumberUtils.GetLongValue(valueInToml); - if (!nullableDouble.HasValue) - return null; + if (!nullableDouble.HasValue) + return null; - return new TomlLong(nullableDouble.Value); - } + return new TomlLong(nullableDouble.Value); + } - public long Value => _value; - public override string StringValue => Value.ToString(); + public long Value => _value; + public override string StringValue => Value.ToString(); - public override string SerializedValue => StringValue; - } + public override string SerializedValue => StringValue; } \ No newline at end of file diff --git a/Tomlet/Models/TomlOffsetDateTime.cs b/Tomlet/Models/TomlOffsetDateTime.cs index 4967427..a9ce9b1 100644 --- a/Tomlet/Models/TomlOffsetDateTime.cs +++ b/Tomlet/Models/TomlOffsetDateTime.cs @@ -1,28 +1,27 @@ using System; -namespace Tomlet.Models +namespace Tomlet.Models; + +public class TomlOffsetDateTime : TomlValue { - public class TomlOffsetDateTime : TomlValue - { - private readonly DateTimeOffset _value; + private readonly DateTimeOffset _value; - public TomlOffsetDateTime(DateTimeOffset value) - { - _value = value; - } + public TomlOffsetDateTime(DateTimeOffset value) + { + _value = value; + } - public DateTimeOffset Value => _value; + public DateTimeOffset Value => _value; - public override string StringValue => Value.ToString("O"); + public override string StringValue => Value.ToString("O"); - public static TomlOffsetDateTime? Parse(string input) - { - if (!DateTimeOffset.TryParse(input, out var dt)) - return null; + public static TomlOffsetDateTime? Parse(string input) + { + if (!DateTimeOffset.TryParse(input, out var dt)) + return null; - return new TomlOffsetDateTime(dt); - } - - public override string SerializedValue => StringValue; + return new TomlOffsetDateTime(dt); } + + public override string SerializedValue => StringValue; } \ No newline at end of file diff --git a/Tomlet/Models/TomlString.cs b/Tomlet/Models/TomlString.cs index c0f5d4c..8d66665 100644 --- a/Tomlet/Models/TomlString.cs +++ b/Tomlet/Models/TomlString.cs @@ -2,62 +2,61 @@ using System.Collections.Generic; using Tomlet.Extensions; -namespace Tomlet.Models +namespace Tomlet.Models; + +public class TomlString : TomlValue { - public class TomlString : TomlValue - { - public static TomlString Empty => new(""); + public static TomlString Empty => new(""); - private readonly string _value; + private readonly string _value; - public TomlString(string? value) - { - this._value = value ?? throw new ArgumentNullException(nameof(value), "TomlString's value cannot be null"); - } + public TomlString(string? value) + { + this._value = value ?? throw new ArgumentNullException(nameof(value), "TomlString's value cannot be null"); + } - public string Value => _value; + public string Value => _value; - public override string StringValue => Value; + public override string StringValue => Value; - public override string SerializedValue + public override string SerializedValue + { + get { - get - { - //We cannot just blind serialize this, because we have to decide now if we want to serialize as a literal string or not. - - //If we have backslashes in the string (as in, actual backslashes, not escape sequences) we could consider serializing as a literal string. - //(but not if the string contains a single quote). - //Additionally if it has newlines, we could serialize as a multiline literal - - if (!Value.RuntimeCorrectContains('\'')) - { - //Ok, we could potentially serialize as a literal string - if (Value.RuntimeCorrectContains('\\')) - return Value.RuntimeCorrectContains('\n') ? MultiLineLiteralStringSerializedForm : LiteralStringSerializedForm; - } + //We cannot just blind serialize this, because we have to decide now if we want to serialize as a literal string or not. - //Ok, no special casing, just fall back to sensible defaults: - // If we have single quotes but no double, use a normal string and escape. - // If we have double quotes but no single, and no newlines, use a literal string and no escape. - // If we have double quotes, no single, but newlines, use a multi-line literal. - // Otherwise, use a normal string with escapes. - - if (Value.RuntimeCorrectContains('\'') && !Value.RuntimeCorrectContains('"')) - //Standard string - return StandardStringSerializedForm; - if (Value.RuntimeCorrectContains('"') && !Value.RuntimeCorrectContains('\'') && !Value.RuntimeCorrectContains('\n')) - //Literal - return LiteralStringSerializedForm; - if (Value.RuntimeCorrectContains('"') && !Value.RuntimeCorrectContains('\'')) - //Multi line literal - return MultiLineLiteralStringSerializedForm; + //If we have backslashes in the string (as in, actual backslashes, not escape sequences) we could consider serializing as a literal string. + //(but not if the string contains a single quote). + //Additionally if it has newlines, we could serialize as a multiline literal + + if (!Value.RuntimeCorrectContains('\'')) + { + //Ok, we could potentially serialize as a literal string + if (Value.RuntimeCorrectContains('\\')) + return Value.RuntimeCorrectContains('\n') ? MultiLineLiteralStringSerializedForm : LiteralStringSerializedForm; + } + //Ok, no special casing, just fall back to sensible defaults: + // If we have single quotes but no double, use a normal string and escape. + // If we have double quotes but no single, and no newlines, use a literal string and no escape. + // If we have double quotes, no single, but newlines, use a multi-line literal. + // Otherwise, use a normal string with escapes. + + if (Value.RuntimeCorrectContains('\'') && !Value.RuntimeCorrectContains('"')) + //Standard string return StandardStringSerializedForm; - } + if (Value.RuntimeCorrectContains('"') && !Value.RuntimeCorrectContains('\'') && !Value.RuntimeCorrectContains('\n')) + //Literal + return LiteralStringSerializedForm; + if (Value.RuntimeCorrectContains('"') && !Value.RuntimeCorrectContains('\'')) + //Multi line literal + return MultiLineLiteralStringSerializedForm; + + return StandardStringSerializedForm; } - - internal string StandardStringSerializedForm => $"\"{TomlUtils.EscapeStringValue(Value)}\""; - internal string LiteralStringSerializedForm => $"'{Value}'"; - internal string MultiLineLiteralStringSerializedForm => $"'''\n{Value}'''"; } + + internal string StandardStringSerializedForm => $"\"{TomlUtils.EscapeStringValue(Value)}\""; + internal string LiteralStringSerializedForm => $"'{Value}'"; + internal string MultiLineLiteralStringSerializedForm => $"'''\n{Value}'''"; } \ No newline at end of file diff --git a/Tomlet/Models/TomlTable.cs b/Tomlet/Models/TomlTable.cs index 210ef7b..67fac2d 100644 --- a/Tomlet/Models/TomlTable.cs +++ b/Tomlet/Models/TomlTable.cs @@ -7,469 +7,476 @@ using Tomlet.Exceptions; using Tomlet.Extensions; -namespace Tomlet.Models +namespace Tomlet.Models; + +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public class TomlTable : TomlValue, IEnumerable> { - [SuppressMessage("ReSharper", "UnusedMember.Global")] - public class TomlTable : TomlValue, IEnumerable> - { - public readonly Dictionary Entries = new(); + public readonly Dictionary Entries = new(); - internal bool Locked; - internal bool Defined; + internal bool Locked; + internal bool Defined; - public bool ForceNoInline { get; set; } + public bool ForceNoInline { get; set; } - public override string StringValue => $"Table ({Entries.Count} entries)"; + public override string StringValue => $"Table ({Entries.Count} entries)"; - public HashSet Keys => new(Entries.Keys); + public HashSet Keys => new(Entries.Keys); - public bool ShouldBeSerializedInline => !ForceNoInline && Entries.Count < 4 - && Entries.All(e => !e.Key.Contains(" ") - && e.Value.Comments.ThereAreNoComments - && (e.Value is TomlArray arr ? arr.IsSimpleArray : e.Value is not TomlTable)); + public bool ShouldBeSerializedInline => !ForceNoInline && Entries.Count < 4 + && Entries.All(e => !e.Key.Contains(" ") + && e.Value.Comments.ThereAreNoComments + && (e.Value is TomlArray arr ? arr.IsSimpleArray : e.Value is not TomlTable)); - public override string SerializedValue + public override string SerializedValue + { + get { - get - { - if (!ShouldBeSerializedInline) - throw new Exception("Cannot use SerializeValue to serialize non-inline tables. Use SerializeNonInlineTable(keyName)."); + if (!ShouldBeSerializedInline) + throw new Exception("Cannot use SerializeValue to serialize non-inline tables. Use SerializeNonInlineTable(keyName)."); - var builder = new StringBuilder("{ "); + var builder = new StringBuilder("{ "); - builder.Append(string.Join(", ", Entries.Select(o => EscapeKeyIfNeeded(o.Key) + " = " + o.Value.SerializedValue).ToArray())); + builder.Append(string.Join(", ", Entries.Select(o => EscapeKeyIfNeeded(o.Key) + " = " + o.Value.SerializedValue).ToArray())); - builder.Append(" }"); + builder.Append(" }"); - return builder.ToString(); - } + return builder.ToString(); } + } - public string SerializeNonInlineTable(string? keyName, bool includeHeader = true) + public string SerializeNonInlineTable(string? keyName, bool includeHeader = true) + { + var builder = new StringBuilder(); + if (includeHeader) { - var builder = new StringBuilder(); - if (includeHeader) - { - builder.Append('[').Append(keyName).Append("]"); - - //For non-inline tables, the inline comment goes on the header line. - if (Comments.InlineComment != null) - builder.Append(" # ").Append(Comments.InlineComment); - - builder.Append('\n'); - } + builder.Append('[').Append(keyName).Append("]"); - //Three passes: Simple key-value pairs including inline arrays and tables, sub-tables, then sub-table-arrays. - foreach (var (subKey, value) in Entries) - { - if (value is TomlTable { ShouldBeSerializedInline: false } or TomlArray { CanBeSerializedInline: false }) - continue; - - WriteValueToStringBuilder(keyName, subKey, builder); - } + //For non-inline tables, the inline comment goes on the header line. + if (Comments.InlineComment != null) + builder.Append(" # ").Append(Comments.InlineComment); - foreach (var (subKey, value) in Entries) - { - if (value is not TomlTable { ShouldBeSerializedInline: false }) - continue; + builder.Append('\n'); + } - WriteValueToStringBuilder(keyName, subKey, builder); - } + //Three passes: Simple key-value pairs including inline arrays and tables, sub-tables, then sub-table-arrays. + foreach (var (subKey, value) in Entries) + { + if (value is TomlTable { ShouldBeSerializedInline: false } or TomlArray { CanBeSerializedInline: false }) + continue; - foreach (var (subKey, value) in Entries) - { - if (value is not TomlArray { CanBeSerializedInline: false }) - continue; + WriteValueToStringBuilder(keyName, subKey, builder); + } - WriteValueToStringBuilder(keyName, subKey, builder); - } + foreach (var (subKey, value) in Entries) + { + if (value is not TomlTable { ShouldBeSerializedInline: false }) + continue; - return builder.ToString(); + WriteValueToStringBuilder(keyName, subKey, builder); } - private void WriteValueToStringBuilder(string? keyName, string subKey, StringBuilder builder) + foreach (var (subKey, value) in Entries) { - var value = GetValue(subKey); + if (value is not TomlArray { CanBeSerializedInline: false }) + continue; - subKey = EscapeKeyIfNeeded(subKey); + WriteValueToStringBuilder(keyName, subKey, builder); + } - if (keyName != null) - keyName = EscapeKeyIfNeeded(keyName); + return builder.ToString(); + } - var fullSubKey = keyName == null ? subKey : $"{keyName}.{subKey}"; + private void WriteValueToStringBuilder(string? keyName, string subKey, StringBuilder builder) + { + var value = GetValue(subKey); - var hadBlankLine = builder.Length < 2 || builder[builder.Length - 2] == '\n'; + subKey = EscapeKeyIfNeeded(subKey); - //Handle any preceding comment - this will ALWAYS go before any sort of value - if (value.Comments.PrecedingComment != null) - builder.Append(value.Comments.FormatPrecedingComment()) - .Append('\n'); + if (keyName != null) + keyName = EscapeKeyIfNeeded(keyName); - switch (value) - { - case TomlArray { CanBeSerializedInline: false } subArray: - if (!hadBlankLine) - builder.Append('\n'); - - builder.Append(subArray.SerializeTableArray(fullSubKey)); //No need to append newline as SerializeTableArray always ensure it ends with 2 - return; //Return because we don't do newline or handle inline comment here. - case TomlArray subArray: - builder.Append(subKey).Append(" = ").Append(subArray.SerializedValue); - break; - case TomlTable { ShouldBeSerializedInline: true } subTable: - builder.Append(subKey).Append(" = ").Append(subTable.SerializedValue); - break; - case TomlTable subTable: - builder.Append(subTable.SerializeNonInlineTable(fullSubKey)).Append('\n'); - return; //Return because we don't handle inline comment here. - default: - builder.Append(subKey).Append(" = ").Append(value.SerializedValue); - break; - } + var fullSubKey = keyName == null ? subKey : $"{keyName}.{subKey}"; - //If we're here we did something resembling an inline value, even if that value is actually a multi-line array. + var hadBlankLine = builder.Length < 2 || builder[builder.Length - 2] == '\n'; - //First off, handle the inline comment. - if (value.Comments.InlineComment != null) - builder.Append(" # ").Append(value.Comments.InlineComment); + //Handle any preceding comment - this will ALWAYS go before any sort of value + if (value.Comments.PrecedingComment != null) + builder.Append(value.Comments.FormatPrecedingComment()) + .Append('\n'); - //Then append a newline - builder.Append('\n'); + switch (value) + { + case TomlArray { CanBeSerializedInline: false } subArray: + if (!hadBlankLine) + builder.Append('\n'); + + builder.Append(subArray.SerializeTableArray(fullSubKey)); //No need to append newline as SerializeTableArray always ensure it ends with 2 + return; //Return because we don't do newline or handle inline comment here. + case TomlArray subArray: + builder.Append(subKey).Append(" = ").Append(subArray.SerializedValue); + break; + case TomlTable { ShouldBeSerializedInline: true } subTable: + builder.Append(subKey).Append(" = ").Append(subTable.SerializedValue); + break; + case TomlTable subTable: + builder.Append(subTable.SerializeNonInlineTable(fullSubKey)).Append('\n'); + return; //Return because we don't handle inline comment here. + default: + builder.Append(subKey).Append(" = ").Append(value.SerializedValue); + break; } - private static string EscapeKeyIfNeeded(string key) - { - if (key.StartsWith("\"") && key.EndsWith("\"") && key.Count(c => c == '"') == 2) - //Already double quoted - return key; + //If we're here we did something resembling an inline value, even if that value is actually a multi-line array. + + //First off, handle the inline comment. + if (value.Comments.InlineComment != null) + builder.Append(" # ").Append(value.Comments.InlineComment); + + //Then append a newline + builder.Append('\n'); + } - if (key.StartsWith("'") && key.EndsWith("'") && key.Count(c => c == '\'') == 2) - //Already single quoted - return key; + private static string EscapeKeyIfNeeded(string key) + { + if (key.StartsWith("\"") && key.EndsWith("\"") && key.Count(c => c == '"') == 2) + //Already double quoted + return key; + + if (key.StartsWith("'") && key.EndsWith("'") && key.Count(c => c == '\'') == 2) + //Already single quoted + return key; - if (IsValidKey(key)) - return key; + if (IsValidKey(key)) + return key; - key = TomlUtils.EscapeStringValue(key); - return TomlUtils.AddCorrectQuotes(key); - } + key = TomlUtils.EscapeStringValue(key); + return TomlUtils.AddCorrectQuotes(key); + } - private static bool IsValidKey(string key) + private static bool IsValidKey(string key) + { + foreach (var c in key) { - foreach (var c in key) + //TODO Future: This check for period is perhaps not super valid but it was way more broken without it so I'm leaving it in for now. + if (!char.IsLetterOrDigit(c) && c != '_' && c != '-' && c != '.') { - //TODO Future: This check for period is perhaps not super valid but it was way more broken without it so I'm leaving it in for now. - if (!char.IsLetterOrDigit(c) && c != '_' && c != '-' && c != '.') - { - return false; - } + return false; } - - return true; } - internal void ParserPutValue(string key, TomlValue value, int lineNumber) - { - if (Locked) - throw new TomlTableLockedException(lineNumber, key); + return true; + } - InternalPutValue(key, value, lineNumber, true); - } + internal void ParserPutValue(string key, TomlValue value, int lineNumber) + { + if (Locked) + throw new TomlTableLockedException(lineNumber, key); - public void PutValue(string key, TomlValue value, bool quote = false) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); + InternalPutValue(key, value, lineNumber, true); + } - if (value == null) - throw new ArgumentNullException(nameof(value)); + public void PutValue(string key, TomlValue value, bool quote = false) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); - if (quote) - key = TomlUtils.AddCorrectQuotes(key); - InternalPutValue(key, value, null, false); - } + if (value == null) + throw new ArgumentNullException(nameof(value)); - public void Put(string key, T t, bool quote = false) - { - TomlValue? tomlValue; - tomlValue = t is not TomlValue tv ? TomletMain.ValueFrom(t) : tv; + if (quote) + key = TomlUtils.AddCorrectQuotes(key); + InternalPutValue(key, value, null, false); + } + +#if MODERN_DOTNET + public void Put<[DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] T>(string key, T t, bool quote = false) +#else + public void Put(string key, T t, bool quote = false) +#endif + { + TomlValue? tomlValue; + tomlValue = t is not TomlValue tv ? TomletMain.ValueFrom(t) : tv; - if (tomlValue == null) - throw new ArgumentException("Value to insert into TOML table serialized to null.", nameof(t)); + if (tomlValue == null) + throw new ArgumentException("Value to insert into TOML table serialized to null.", nameof(t)); - PutValue(key, tomlValue, quote); - } + PutValue(key, tomlValue, quote); + } - public string DeQuoteKey(string key) - { - var wholeKeyIsQuoted = key.StartsWith("\"") && key.EndsWith("\"") || key.StartsWith("'") && key.EndsWith("'"); - return !wholeKeyIsQuoted ? key : key.Substring(1, key.Length - 2); - } + public string DeQuoteKey(string key) + { + var wholeKeyIsQuoted = key.StartsWith("\"") && key.EndsWith("\"") || key.StartsWith("'") && key.EndsWith("'"); + return !wholeKeyIsQuoted ? key : key.Substring(1, key.Length - 2); + } - private void InternalPutValue(string key, TomlValue value, int? lineNumber, bool callParserForm) - { - key = key.Trim(); - TomlKeyUtils.GetTopLevelAndSubKeys(key, out var ourKeyName, out var restOfKey); + private void InternalPutValue(string key, TomlValue value, int? lineNumber, bool callParserForm) + { + key = key.Trim(); + TomlKeyUtils.GetTopLevelAndSubKeys(key, out var ourKeyName, out var restOfKey); - if (!string.IsNullOrEmpty(restOfKey)) + if (!string.IsNullOrEmpty(restOfKey)) + { + if (!Entries.TryGetValue(DeQuoteKey(ourKeyName), out var existingValue)) { - if (!Entries.TryGetValue(DeQuoteKey(ourKeyName), out var existingValue)) - { - //We don't have a sub-table with this name defined. That's fine, make one. - var subtable = new TomlTable(); - if (callParserForm) - ParserPutValue(ourKeyName, subtable, lineNumber!.Value); - else - PutValue(ourKeyName, subtable); - - //And tell it to handle the rest of the key. - if (callParserForm) - subtable.ParserPutValue(restOfKey, value, lineNumber!.Value); - else - subtable.PutValue(restOfKey, value); - return; - } - - //We have a key by this name already. Is it a table? - if (existingValue is not TomlTable existingTable) - { - //No - throw an exception - if (lineNumber.HasValue) - throw new TomlDottedKeyParserException(lineNumber.Value, ourKeyName); - - throw new TomlDottedKeyException(ourKeyName); - } - - //Yes, get the sub-table to handle the rest of the key + //We don't have a sub-table with this name defined. That's fine, make one. + var subtable = new TomlTable(); + if (callParserForm) + ParserPutValue(ourKeyName, subtable, lineNumber!.Value); + else + PutValue(ourKeyName, subtable); + + //And tell it to handle the rest of the key. if (callParserForm) - existingTable.ParserPutValue(restOfKey, value, lineNumber!.Value); + subtable.ParserPutValue(restOfKey, value, lineNumber!.Value); else - existingTable.PutValue(restOfKey, value); + subtable.PutValue(restOfKey, value); return; } - //Non-dotted keys land here. - key = DeQuoteKey(key); + //We have a key by this name already. Is it a table? + if (existingValue is not TomlTable existingTable) + { + //No - throw an exception + if (lineNumber.HasValue) + throw new TomlDottedKeyParserException(lineNumber.Value, ourKeyName); - if (Entries.ContainsKey(key) && lineNumber.HasValue) - throw new TomlKeyRedefinitionException(lineNumber.Value, key); + throw new TomlDottedKeyException(ourKeyName); + } - Entries[key] = value; + //Yes, get the sub-table to handle the rest of the key + if (callParserForm) + existingTable.ParserPutValue(restOfKey, value, lineNumber!.Value); + else + existingTable.PutValue(restOfKey, value); + return; } - public bool ContainsKey(string key) - { - if (key == null) - throw new ArgumentNullException("key"); + //Non-dotted keys land here. + key = DeQuoteKey(key); - TomlKeyUtils.GetTopLevelAndSubKeys(key, out var ourKeyName, out var restOfKey); + if (Entries.ContainsKey(key) && lineNumber.HasValue) + throw new TomlKeyRedefinitionException(lineNumber.Value, key); - if (string.IsNullOrEmpty(restOfKey)) - //Non-dotted key - return Entries.ContainsKey(DeQuoteKey(key)); + Entries[key] = value; + } - if (!Entries.TryGetValue(ourKeyName, out var existingKey)) - return false; + public bool ContainsKey(string key) + { + if (key == null) + throw new ArgumentNullException("key"); - if (existingKey is TomlTable table) - return table.ContainsKey(restOfKey); + TomlKeyUtils.GetTopLevelAndSubKeys(key, out var ourKeyName, out var restOfKey); - throw new TomlContainsDottedKeyNonTableException(key); - } + if (string.IsNullOrEmpty(restOfKey)) + //Non-dotted key + return Entries.ContainsKey(DeQuoteKey(key)); + + if (!Entries.TryGetValue(ourKeyName, out var existingKey)) + return false; + + if (existingKey is TomlTable table) + return table.ContainsKey(restOfKey); + + throw new TomlContainsDottedKeyNonTableException(key); + } -#if NET6_0 +#if MODERN_DOTNET public bool TryGetValue(string key, [NotNullWhen(true)] out TomlValue? value) #else - public bool TryGetValue(string key, out TomlValue? value) + public bool TryGetValue(string key, out TomlValue value) #endif - { - if (ContainsKey(key)) - return (value = GetValue(key)) != null; + { + if (ContainsKey(key)) + return (value = GetValue(key)) != null; +#if MODERN_DOTNET value = null; - return false; - } +#else + value = null!; //Not null asserting because this is the failure case, callers are expected to check the return value, and this is only on old frameworks that don't support NotNullWhen +#endif + return false; + } - /// - /// Returns the raw instance of associated with this key. You must cast to a sub-class and access its value - /// yourself. - /// Unlike all the specific getters, this Getter respects dotted keys and quotes. You must quote any keys which contain a dot if you want to access the key itself, - /// not a sub-key. - /// - /// The key to look up. - /// An instance of associated with this key. - /// If the key is not present in the table. - public TomlValue GetValue(string key) - { - if (key == null) - throw new ArgumentNullException("key"); + /// + /// Returns the raw instance of associated with this key. You must cast to a sub-class and access its value + /// yourself. + /// Unlike all the specific getters, this Getter respects dotted keys and quotes. You must quote any keys which contain a dot if you want to access the key itself, + /// not a sub-key. + /// + /// The key to look up. + /// An instance of associated with this key. + /// If the key is not present in the table. + public TomlValue GetValue(string key) + { + if (key == null) + throw new ArgumentNullException("key"); - if (!ContainsKey(key)) - throw new TomlNoSuchValueException(key); + if (!ContainsKey(key)) + throw new TomlNoSuchValueException(key); - TomlKeyUtils.GetTopLevelAndSubKeys(key, out var ourKeyName, out var restOfKey); + TomlKeyUtils.GetTopLevelAndSubKeys(key, out var ourKeyName, out var restOfKey); - if (string.IsNullOrEmpty(restOfKey)) - //Non-dotted key - return Entries[DeQuoteKey(key)]; + if (string.IsNullOrEmpty(restOfKey)) + //Non-dotted key + return Entries[DeQuoteKey(key)]; - if (!Entries.TryGetValue(ourKeyName, out var existingKey)) - throw new TomlNoSuchValueException(key); //Should already be handled by ContainsKey test + if (!Entries.TryGetValue(ourKeyName, out var existingKey)) + throw new TomlNoSuchValueException(key); //Should already be handled by ContainsKey test - if (existingKey is TomlTable table) - return table.GetValue(restOfKey); + if (existingKey is TomlTable table) + return table.GetValue(restOfKey); - throw new Exception("Tomlet Internal bug - existing key is not a table in TomlTable GetValue, but we didn't throw in ContainsKey?"); - } + throw new Exception("Tomlet Internal bug - existing key is not a table in TomlTable GetValue, but we didn't throw in ContainsKey?"); + } - /// - /// Returns the string value associated with the provided key. - /// - /// The key to look up. - /// The string value associated with the key. - /// If the value associated with this key is not a string. - /// If the key is not present in the table. - public string GetString(string key) - { - if (key == null) - throw new ArgumentNullException("key"); + /// + /// Returns the string value associated with the provided key. + /// + /// The key to look up. + /// The string value associated with the key. + /// If the value associated with this key is not a string. + /// If the key is not present in the table. + public string GetString(string key) + { + if (key == null) + throw new ArgumentNullException("key"); - var value = GetValue(TomlUtils.AddCorrectQuotes(key)); + var value = GetValue(TomlUtils.AddCorrectQuotes(key)); - if (value is not TomlString str) - throw new TomlTypeMismatchException(typeof(TomlString), value.GetType(), typeof(string)); + if (value is not TomlString str) + throw new TomlTypeMismatchException(typeof(TomlString), value.GetType(), typeof(string)); - return str.Value; - } + return str.Value; + } - /// - /// Returns the integer value associated with the provided key, downsized from a long. - /// - /// The key to look up. - /// The integer value associated with the key. - /// If the value associated with this key is not an integer type. - /// If the key is not present in the table. - public int GetInteger(string key) - { - if (key == null) - throw new ArgumentNullException("key"); + /// + /// Returns the integer value associated with the provided key, downsized from a long. + /// + /// The key to look up. + /// The integer value associated with the key. + /// If the value associated with this key is not an integer type. + /// If the key is not present in the table. + public int GetInteger(string key) + { + if (key == null) + throw new ArgumentNullException("key"); - var value = GetValue(TomlUtils.AddCorrectQuotes(key)); + var value = GetValue(TomlUtils.AddCorrectQuotes(key)); - if (value is not TomlLong lng) - throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(int)); + if (value is not TomlLong lng) + throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(int)); - return (int)lng.Value; - } + return (int)lng.Value; + } - /// - /// Returns the long (64-bit int) value associated with the provided key. - /// - /// The key to look up. - /// The long/64-bit integer value associated with the key. - /// If the value associated with this key is not an integer type. - /// If the key is not present in the table. - public long GetLong(string key) - { - if (key == null) - throw new ArgumentNullException("key"); + /// + /// Returns the long (64-bit int) value associated with the provided key. + /// + /// The key to look up. + /// The long/64-bit integer value associated with the key. + /// If the value associated with this key is not an integer type. + /// If the key is not present in the table. + public long GetLong(string key) + { + if (key == null) + throw new ArgumentNullException("key"); - var value = GetValue(TomlUtils.AddCorrectQuotes(key)); + var value = GetValue(TomlUtils.AddCorrectQuotes(key)); - if (value is not TomlLong lng) - throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(int)); + if (value is not TomlLong lng) + throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(int)); - return lng.Value; - } + return lng.Value; + } - /// - /// Returns the 32-bit floating-point value associated with the provided key, downsized from a double. - /// - /// The key to look up. - /// The float value associated with the key. - /// If the value associated with this key is not a floating-point type. - /// If the key is not present in the table. - public float GetFloat(string key) - { - if (key == null) - throw new ArgumentNullException("key"); + /// + /// Returns the 32-bit floating-point value associated with the provided key, downsized from a double. + /// + /// The key to look up. + /// The float value associated with the key. + /// If the value associated with this key is not a floating-point type. + /// If the key is not present in the table. + public float GetFloat(string key) + { + if (key == null) + throw new ArgumentNullException("key"); - var value = GetValue(TomlUtils.AddCorrectQuotes(key)); + var value = GetValue(TomlUtils.AddCorrectQuotes(key)); - if (value is not TomlDouble dbl) - throw new TomlTypeMismatchException(typeof(TomlDouble), value.GetType(), typeof(float)); + if (value is not TomlDouble dbl) + throw new TomlTypeMismatchException(typeof(TomlDouble), value.GetType(), typeof(float)); - return (float)dbl.Value; - } + return (float)dbl.Value; + } - /// - /// Returns the boolean value associated with the provided key - /// - /// The key to look up. - /// The boolean value associated with the key. - /// If the value associated with this key is not a boolean. - /// If the key is not present in the table. - public bool GetBoolean(string key) - { - if (key == null) - throw new ArgumentNullException("key"); + /// + /// Returns the boolean value associated with the provided key + /// + /// The key to look up. + /// The boolean value associated with the key. + /// If the value associated with this key is not a boolean. + /// If the key is not present in the table. + public bool GetBoolean(string key) + { + if (key == null) + throw new ArgumentNullException("key"); - var value = GetValue(TomlUtils.AddCorrectQuotes(key)); + var value = GetValue(TomlUtils.AddCorrectQuotes(key)); - if (value is not TomlBoolean b) - throw new TomlTypeMismatchException(typeof(TomlBoolean), value.GetType(), typeof(bool)); + if (value is not TomlBoolean b) + throw new TomlTypeMismatchException(typeof(TomlBoolean), value.GetType(), typeof(bool)); - return b.Value; - } + return b.Value; + } - /// - /// Returns the TOML array associated with the provided key. - /// - /// The key to look up. - /// The TOML array associated with the key. - /// If the value associated with this key is not an array. - /// If the key is not present in the table. - public TomlArray GetArray(string key) - { - if (key == null) - throw new ArgumentNullException("key"); + /// + /// Returns the TOML array associated with the provided key. + /// + /// The key to look up. + /// The TOML array associated with the key. + /// If the value associated with this key is not an array. + /// If the key is not present in the table. + public TomlArray GetArray(string key) + { + if (key == null) + throw new ArgumentNullException("key"); - var value = GetValue(TomlUtils.AddCorrectQuotes(key)); + var value = GetValue(TomlUtils.AddCorrectQuotes(key)); - if (value is not TomlArray arr) - throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), typeof(TomlArray)); + if (value is not TomlArray arr) + throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), typeof(TomlArray)); - return arr; - } + return arr; + } - /// - /// Returns the TOML table associated with the provided key. - /// - /// The key to look up. - /// The TOML table associated with the key. - /// If the value associated with this key is not a table. - /// If the key is not present in the table. - public TomlTable GetSubTable(string key) - { - if (key == null) - throw new ArgumentNullException("key"); + /// + /// Returns the TOML table associated with the provided key. + /// + /// The key to look up. + /// The TOML table associated with the key. + /// If the value associated with this key is not a table. + /// If the key is not present in the table. + public TomlTable GetSubTable(string key) + { + if (key == null) + throw new ArgumentNullException("key"); - var value = GetValue(TomlUtils.AddCorrectQuotes(key)); + var value = GetValue(TomlUtils.AddCorrectQuotes(key)); - if (value is not TomlTable tbl) - throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(TomlTable)); + if (value is not TomlTable tbl) + throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(TomlTable)); - return tbl; - } + return tbl; + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - public IEnumerator> GetEnumerator() - { - return Entries.GetEnumerator(); - } + public IEnumerator> GetEnumerator() + { + return Entries.GetEnumerator(); } } \ No newline at end of file diff --git a/Tomlet/Models/TomlValue.cs b/Tomlet/Models/TomlValue.cs index 44f6156..a6f9c11 100644 --- a/Tomlet/Models/TomlValue.cs +++ b/Tomlet/Models/TomlValue.cs @@ -1,25 +1,24 @@ -namespace Tomlet.Models +namespace Tomlet.Models; + +public abstract class TomlValue { - public abstract class TomlValue - { - public TomlCommentData Comments { get; } = new(); + public TomlCommentData Comments { get; } = new(); - public abstract string StringValue - { - get; - } + public abstract string StringValue + { + get; + } - /// - /// The value that should be used to represent this instance when it is written to a TOML file. - /// - public abstract string SerializedValue - { - get; - } + /// + /// The value that should be used to represent this instance when it is written to a TOML file. + /// + public abstract string SerializedValue + { + get; + } - public override string ToString() - { - return StringValue; - } + public override string ToString() + { + return StringValue; } } \ No newline at end of file diff --git a/Tomlet/Models/TomlValueWithDateTime.cs b/Tomlet/Models/TomlValueWithDateTime.cs index 43323c3..223d719 100644 --- a/Tomlet/Models/TomlValueWithDateTime.cs +++ b/Tomlet/Models/TomlValueWithDateTime.cs @@ -1,9 +1,8 @@ using System; -namespace Tomlet.Models +namespace Tomlet.Models; + +public interface ITomlValueWithDateTime { - public interface ITomlValueWithDateTime - { - public DateTime Value { get; } - } + public DateTime Value { get; } } \ No newline at end of file diff --git a/Tomlet/TomlCompositeDeserializer.cs b/Tomlet/TomlCompositeDeserializer.cs index 4431174..f705fa4 100644 --- a/Tomlet/TomlCompositeDeserializer.cs +++ b/Tomlet/TomlCompositeDeserializer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -12,7 +13,12 @@ namespace Tomlet; internal static class TomlCompositeDeserializer { +#if MODERN_DOTNET + [UnconditionalSuppressMessage("AOT", "IL2072", Justification = "Any field that is being deserialized to must have been used as a field in the consuming code in order for the code path that queries it to run, so the dynamic code requirement is already satisfied.")] + public static TomlSerializationMethods.Deserialize For([DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] Type type, TomlSerializerOptions options) +#else public static TomlSerializationMethods.Deserialize For(Type type, TomlSerializerOptions options) +#endif { TomlSerializationMethods.Deserialize deserializer; if (type.IsEnum) @@ -74,7 +80,7 @@ public static TomlSerializationMethods.Deserialize For(Type type, TomlSe object fieldValue; try { - fieldValue = TomlSerializationMethods.GetDeserializer(field.FieldType, options).Invoke(entry!); + fieldValue = TomlSerializationMethods.GetDeserializer(field.FieldType, options).Invoke(entry); } catch (TomlTypeMismatchException e) { @@ -97,7 +103,7 @@ public static TomlSerializationMethods.Deserialize For(Type type, TomlSe try { - propValue = TomlSerializationMethods.GetDeserializer(prop.PropertyType, options).Invoke(entry!); + propValue = TomlSerializationMethods.GetDeserializer(prop.PropertyType, options).Invoke(entry); } catch (TomlTypeMismatchException e) { @@ -117,7 +123,12 @@ public static TomlSerializationMethods.Deserialize For(Type type, TomlSe return deserializer; } +#if MODERN_DOTNET + [UnconditionalSuppressMessage("AOT", "IL2072", Justification = "Any constructor parameter must have been used somewhere in the consuming code in order for the code path that queries it to run, so the dynamic code requirement is already satisfied.")] + private static object CreateInstance([DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] Type type, TomlValue tomlValue, TomlSerializerOptions options, out HashSet assignedMembers) +#else private static object CreateInstance(Type type, TomlValue tomlValue, TomlSerializerOptions options, out HashSet assignedMembers) +#endif { if (tomlValue is not TomlTable table) throw new TomlTypeMismatchException(typeof(TomlTable), tomlValue.GetType(), type); @@ -146,7 +157,7 @@ private static object CreateInstance(Type type, TomlValue tomlValue, TomlSeriali try { - argument = TomlSerializationMethods.GetDeserializer(parameter.ParameterType, options).Invoke(entry!); + argument = TomlSerializationMethods.GetDeserializer(parameter.ParameterType, options).Invoke(entry); } catch (TomlTypeMismatchException e) { diff --git a/Tomlet/TomlCompositeSerializer.cs b/Tomlet/TomlCompositeSerializer.cs index bcb034b..f92f254 100644 --- a/Tomlet/TomlCompositeSerializer.cs +++ b/Tomlet/TomlCompositeSerializer.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -10,7 +11,12 @@ namespace Tomlet; internal static class TomlCompositeSerializer { +#if MODERN_DOTNET + [UnconditionalSuppressMessage("AOT", "IL2072", Justification = "Any field that is being serialized must have been used as a field in the consuming code in order for the code path that queries it to run, so the dynamic code requirement is already satisfied.")] + public static TomlSerializationMethods.Serialize For([DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] Type type, TomlSerializerOptions options) +#else public static TomlSerializationMethods.Serialize For(Type type, TomlSerializerOptions options) +#endif { TomlSerializationMethods.Serialize serializer; diff --git a/Tomlet/TomlDateTimeUtils.cs b/Tomlet/TomlDateTimeUtils.cs index e636a64..9375f61 100644 --- a/Tomlet/TomlDateTimeUtils.cs +++ b/Tomlet/TomlDateTimeUtils.cs @@ -3,54 +3,53 @@ using Tomlet.Extensions; using Tomlet.Models; -namespace Tomlet +namespace Tomlet; + +internal static class TomlDateTimeUtils { - internal static class TomlDateTimeUtils + private static readonly Regex DateTimeRegex = new( + @"^(?:(\d+)-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01]))?([\sTt])?(?:([01]\d|2[0-3]):([0-5]\d):([0-5]\d|60)(\.\d+)?((?:[Zz])|(?:[\+|\-](?:[01]\d|2[0-3])(?::[0-6][0-9])?(?::[0-6][0-9])?))?)?$", + RegexOptions.Compiled + ); + + internal static TomlValue? ParseDateString(string input, int lineNumber) { - private static readonly Regex DateTimeRegex = new( - @"^(?:(\d+)-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01]))?([\sTt])?(?:([01]\d|2[0-3]):([0-5]\d):([0-5]\d|60)(\.\d+)?((?:[Zz])|(?:[\+|\-](?:[01]\d|2[0-3])(?::[0-6][0-9])?(?::[0-6][0-9])?))?)?$", - RegexOptions.Compiled - ); - - internal static TomlValue? ParseDateString(string input, int lineNumber) - { - //All groups can be empty. - //Group 1 - Year - //Group 2 - Month - //Group 3 - Day - //Group 4 - Date/Time separator. If empty string and both date and time present, syntax error. - //Group 5 - Hour - //Group 6 - Minute - //Group 7 - Second - //Group 8 - Milliseconds - //Group 9 - Time zone - if not present, this is a local (date)time. If present without a date or without a time, syntax error. - var match = DateTimeRegex.Match(input); - - //If year is present, whole date has to be by the regex. - var hasYear = !match.Groups[1].Value.IsNullOrWhiteSpace(); - var hasSeparator = !string.IsNullOrEmpty(match.Groups[4].Value); - var hasHour = !match.Groups[5].Value.IsNullOrWhiteSpace(); - var hasTimezone = !match.Groups[9].Value.IsNullOrWhiteSpace(); - - if (hasYear && hasHour && !hasSeparator) - throw new TomlDateTimeMissingSeparatorException(lineNumber); - - if (hasSeparator && (!hasHour || !hasYear)) - throw new TomlDateTimeUnnecessarySeparatorException(lineNumber); - - if (hasTimezone && (!hasHour || !hasYear)) - throw new TimeOffsetOnTomlDateOrTimeException(lineNumber, match.Groups[9].Value); - - if (!hasYear) - return TomlLocalTime.Parse(input); - - if (!hasHour) - return TomlLocalDate.Parse(input); - - if (!hasTimezone) - return TomlLocalDateTime.Parse(input); - - return TomlOffsetDateTime.Parse(input); - } + //All groups can be empty. + //Group 1 - Year + //Group 2 - Month + //Group 3 - Day + //Group 4 - Date/Time separator. If empty string and both date and time present, syntax error. + //Group 5 - Hour + //Group 6 - Minute + //Group 7 - Second + //Group 8 - Milliseconds + //Group 9 - Time zone - if not present, this is a local (date)time. If present without a date or without a time, syntax error. + var match = DateTimeRegex.Match(input); + + //If year is present, whole date has to be by the regex. + var hasYear = !match.Groups[1].Value.IsNullOrWhiteSpace(); + var hasSeparator = !string.IsNullOrEmpty(match.Groups[4].Value); + var hasHour = !match.Groups[5].Value.IsNullOrWhiteSpace(); + var hasTimezone = !match.Groups[9].Value.IsNullOrWhiteSpace(); + + if (hasYear && hasHour && !hasSeparator) + throw new TomlDateTimeMissingSeparatorException(lineNumber); + + if (hasSeparator && (!hasHour || !hasYear)) + throw new TomlDateTimeUnnecessarySeparatorException(lineNumber); + + if (hasTimezone && (!hasHour || !hasYear)) + throw new TimeOffsetOnTomlDateOrTimeException(lineNumber, match.Groups[9].Value); + + if (!hasYear) + return TomlLocalTime.Parse(input); + + if (!hasHour) + return TomlLocalDate.Parse(input); + + if (!hasTimezone) + return TomlLocalDateTime.Parse(input); + + return TomlOffsetDateTime.Parse(input); } } \ No newline at end of file diff --git a/Tomlet/TomlKeyUtils.cs b/Tomlet/TomlKeyUtils.cs index c5b228b..0fa3c13 100644 --- a/Tomlet/TomlKeyUtils.cs +++ b/Tomlet/TomlKeyUtils.cs @@ -1,43 +1,42 @@ using System; -namespace Tomlet +namespace Tomlet; + +internal static class TomlKeyUtils { - internal static class TomlKeyUtils + internal static void GetTopLevelAndSubKeys(string key, out string ourKeyName, out string restOfKey) { - internal static void GetTopLevelAndSubKeys(string key, out string ourKeyName, out string restOfKey) - { - var wholeKeyIsQuoted = key.StartsWith("\"") && key.EndsWith("\"") || key.StartsWith("'") && key.EndsWith("'"); - var firstPartOfKeyIsQuoted = !wholeKeyIsQuoted && (key.StartsWith("\"") || key.StartsWith("'")); + var wholeKeyIsQuoted = key.StartsWith("\"") && key.EndsWith("\"") || key.StartsWith("'") && key.EndsWith("'"); + var firstPartOfKeyIsQuoted = !wholeKeyIsQuoted && (key.StartsWith("\"") || key.StartsWith("'")); - if (!key.Contains(".") || wholeKeyIsQuoted) - { - ourKeyName = key; - restOfKey = ""; - return; - } + if (!key.Contains(".") || wholeKeyIsQuoted) + { + ourKeyName = key; + restOfKey = ""; + return; + } - //Unquoted dotted key means we put this in a sub-table. + //Unquoted dotted key means we put this in a sub-table. - //First get the name of the key in *this* table. - if (!firstPartOfKeyIsQuoted) - { - var split = key.Split('.'); - ourKeyName = split[0]; - } + //First get the name of the key in *this* table. + if (!firstPartOfKeyIsQuoted) + { + var split = key.Split('.'); + ourKeyName = split[0]; + } + else + { + ourKeyName = key; + var keyNameWithoutOpeningQuote = ourKeyName.Substring(1); + if (ourKeyName.Contains("\"")) + ourKeyName = ourKeyName.Substring(0, 2 + keyNameWithoutOpeningQuote.IndexOf("\"", StringComparison.Ordinal)); else - { - ourKeyName = key; - var keyNameWithoutOpeningQuote = ourKeyName.Substring(1); - if (ourKeyName.Contains("\"")) - ourKeyName = ourKeyName.Substring(0, 2 + keyNameWithoutOpeningQuote.IndexOf("\"", StringComparison.Ordinal)); - else - ourKeyName = ourKeyName.Substring(0, 2 + keyNameWithoutOpeningQuote.IndexOf("'", StringComparison.Ordinal)); - } + ourKeyName = ourKeyName.Substring(0, 2 + keyNameWithoutOpeningQuote.IndexOf("'", StringComparison.Ordinal)); + } - //And get the remainder of the key, relative to the sub-table. - restOfKey = key.Substring(ourKeyName.Length + 1); + //And get the remainder of the key, relative to the sub-table. + restOfKey = key.Substring(ourKeyName.Length + 1); - ourKeyName = ourKeyName.Trim(); - } + ourKeyName = ourKeyName.Trim(); } } \ No newline at end of file diff --git a/Tomlet/TomlNumberStyle.cs b/Tomlet/TomlNumberStyle.cs index 80252e3..6475c48 100644 --- a/Tomlet/TomlNumberStyle.cs +++ b/Tomlet/TomlNumberStyle.cs @@ -1,10 +1,9 @@ using System.Globalization; -namespace Tomlet +namespace Tomlet; + +internal static class TomlNumberStyle { - internal static class TomlNumberStyle - { - internal static NumberStyles FloatingPoint = NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign; - internal static NumberStyles Integer = NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign; - } + internal static NumberStyles FloatingPoint = NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign; + internal static NumberStyles Integer = NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign; } \ No newline at end of file diff --git a/Tomlet/TomlNumberUtils.cs b/Tomlet/TomlNumberUtils.cs index 2141b02..b6f2003 100644 --- a/Tomlet/TomlNumberUtils.cs +++ b/Tomlet/TomlNumberUtils.cs @@ -3,98 +3,97 @@ using System.Linq; using Tomlet.Extensions; -namespace Tomlet +namespace Tomlet; + +public static class TomlNumberUtils { - public static class TomlNumberUtils + public static long? GetLongValue(string input) { - public static long? GetLongValue(string input) - { - var isOctal = input.StartsWith("0o"); - var isHex = input.StartsWith("0x"); - var isBinary = input.StartsWith("0b"); + var isOctal = input.StartsWith("0o"); + var isHex = input.StartsWith("0x"); + var isBinary = input.StartsWith("0b"); - if (isBinary || isHex || isOctal) - input = input.Substring(2); + if (isBinary || isHex || isOctal) + input = input.Substring(2); - //Invalid characters, double underscores - if (input.Contains("__") || input.Any(c => !c.IsPermittedInIntegerLiteral())) - return null; + //Invalid characters, double underscores + if (input.Contains("__") || input.Any(c => !c.IsPermittedInIntegerLiteral())) + return null; - //Underscore without a digit before - if (input.First() == '_') - return null; + //Underscore without a digit before + if (input.First() == '_') + return null; - //Underscore without a digit after - if (input.Last() == '_') - return null; + //Underscore without a digit after + if (input.Last() == '_') + return null; - input = input.Replace("_", ""); + input = input.Replace("_", ""); - try - { - if (isBinary) - return Convert.ToInt64(input, 2); + try + { + if (isBinary) + return Convert.ToInt64(input, 2); - if (isOctal) - return Convert.ToInt64(input, 8); + if (isOctal) + return Convert.ToInt64(input, 8); - if (isHex) - return Convert.ToInt64(input, 16); + if (isHex) + return Convert.ToInt64(input, 16); - return Convert.ToInt64(input, 10); - } - catch (Exception) - { - return null; - } + return Convert.ToInt64(input, 10); } + catch (Exception) + { + return null; + } + } - public static double? GetDoubleValue(string input) + public static double? GetDoubleValue(string input) + { + var skippingFirst = input.Substring(1); + + if (input is "nan" or "inf" || skippingFirst is "nan" or "inf") { - var skippingFirst = input.Substring(1); - - if (input is "nan" or "inf" || skippingFirst is "nan" or "inf") - { - //Special value - if (input == "nan" || skippingFirst == "nan") - return double.NaN; - if (input == "inf") - return double.PositiveInfinity; - if (skippingFirst == "inf") - return input.StartsWith("-") ? double.NegativeInfinity : double.PositiveInfinity; - } + //Special value + if (input == "nan" || skippingFirst == "nan") + return double.NaN; + if (input == "inf") + return double.PositiveInfinity; + if (skippingFirst == "inf") + return input.StartsWith("-") ? double.NegativeInfinity : double.PositiveInfinity; + } - if (input.Contains("__") || input.Any(c => !c.IsPermittedInFloatLiteral())) - return null; + if (input.Contains("__") || input.Any(c => !c.IsPermittedInFloatLiteral())) + return null; - //Underscore without a digit before - if (input.First() == '_') - return null; + //Underscore without a digit before + if (input.First() == '_') + return null; - //Underscore without a digit after - if (input.Last() == '_') - return null; + //Underscore without a digit after + if (input.Last() == '_') + return null; - input = input.Replace("_", ""); + input = input.Replace("_", ""); - //We have to do one manual check here because TOML states that decimal points must have a value on the right - //So something like 1.e20 is valid according to the runtime but not according to TOML - if (input.Contains("e")) - { - var parts = input.Split('e'); - if (parts.Length != 2) - return null; - - if (parts[0].EndsWith(".")) - return null; - } - - //Theoretically we can have hex/octal/binary numbers with floating-point parts. I'm not implementing that. - //None of the examples use it. - if (double.TryParse(input, TomlNumberStyle.FloatingPoint, CultureInfo.InvariantCulture, out var val)) - return val; + //We have to do one manual check here because TOML states that decimal points must have a value on the right + //So something like 1.e20 is valid according to the runtime but not according to TOML + if (input.Contains("e")) + { + var parts = input.Split('e'); + if (parts.Length != 2) + return null; - return null; + if (parts[0].EndsWith(".")) + return null; } + + //Theoretically we can have hex/octal/binary numbers with floating-point parts. I'm not implementing that. + //None of the examples use it. + if (double.TryParse(input, TomlNumberStyle.FloatingPoint, CultureInfo.InvariantCulture, out var val)) + return val; + + return null; } } \ No newline at end of file diff --git a/Tomlet/TomlParser.cs b/Tomlet/TomlParser.cs index 4ec7dfe..f3e929d 100644 --- a/Tomlet/TomlParser.cs +++ b/Tomlet/TomlParser.cs @@ -9,1011 +9,1010 @@ using Tomlet.Extensions; using Tomlet.Models; -namespace Tomlet +namespace Tomlet; + +public class TomlParser { - public class TomlParser - { - private static readonly char[] TrueChars = {'t', 'r', 'u', 'e'}; - private static readonly char[] FalseChars = {'f', 'a', 'l', 's', 'e'}; + private static readonly char[] TrueChars = {'t', 'r', 'u', 'e'}; + private static readonly char[] FalseChars = {'f', 'a', 'l', 's', 'e'}; - private int _lineNumber = 1; + private int _lineNumber = 1; - private string[] _tableNames = new string[0]; - private TomlTable? _currentTable; + private string[] _tableNames = new string[0]; + private TomlTable? _currentTable; - // ReSharper disable once UnusedMember.Global - [ExcludeFromCodeCoverage] - public static TomlDocument ParseFile(string filePath) - { - var fileContent = File.ReadAllText(filePath); - TomlParser parser = new(); - return parser.Parse(fileContent); - } + // ReSharper disable once UnusedMember.Global + [ExcludeFromCodeCoverage] + public static TomlDocument ParseFile(string filePath) + { + var fileContent = File.ReadAllText(filePath); + TomlParser parser = new(); + return parser.Parse(fileContent); + } - public TomlDocument Parse(string input) + public TomlDocument Parse(string input) + { + try { - try - { - var document = new TomlDocument(); - using var reader = new TomletStringReader(input); + var document = new TomlDocument(); + using var reader = new TomletStringReader(input); - string? lastPrecedingComment = null; - while (reader.TryPeek(out _)) - { - //We have more to read. - //By the time we get back to this position in the main loop, we've fully consumed any structure. - //So that means we're at the start of a line - which could be a comment, table-array or table header, a key-value pair, or just whitespace. - _lineNumber += reader.SkipAnyNewlineOrWhitespace(); + string? lastPrecedingComment = null; + while (reader.TryPeek(out _)) + { + //We have more to read. + //By the time we get back to this position in the main loop, we've fully consumed any structure. + //So that means we're at the start of a line - which could be a comment, table-array or table header, a key-value pair, or just whitespace. + _lineNumber += reader.SkipAnyNewlineOrWhitespace(); - lastPrecedingComment = ReadAnyPotentialMultilineComment(reader); + lastPrecedingComment = ReadAnyPotentialMultilineComment(reader); - if (!reader.TryPeek(out var nextChar)) - break; + if (!reader.TryPeek(out var nextChar)) + break; - if (nextChar == '[') - { - reader.Read(); //Consume the [ + if (nextChar == '[') + { + reader.Read(); //Consume the [ - //Table or table-array? - if (!reader.TryPeek(out var potentialSecondBracket)) - throw new TomlEndOfFileException(_lineNumber); + //Table or table-array? + if (!reader.TryPeek(out var potentialSecondBracket)) + throw new TomlEndOfFileException(_lineNumber); - TomlValue valueFromSquareBracket; - if (potentialSecondBracket != '[') - valueFromSquareBracket = ReadTableStatement(reader, document); - else - valueFromSquareBracket = ReadTableArrayStatement(reader, document); + TomlValue valueFromSquareBracket; + if (potentialSecondBracket != '[') + valueFromSquareBracket = ReadTableStatement(reader, document); + else + valueFromSquareBracket = ReadTableArrayStatement(reader, document); - valueFromSquareBracket.Comments.PrecedingComment = lastPrecedingComment; + valueFromSquareBracket.Comments.PrecedingComment = lastPrecedingComment; - continue; //Restart loop. - } + continue; //Restart loop. + } - //Read a key-value pair - ReadKeyValuePair(reader, out var key, out var value); + //Read a key-value pair + ReadKeyValuePair(reader, out var key, out var value); - value.Comments.PrecedingComment = lastPrecedingComment; - lastPrecedingComment = null; + value.Comments.PrecedingComment = lastPrecedingComment; + lastPrecedingComment = null; - if (_currentTable != null) - //Insert into current table - _currentTable.ParserPutValue(key, value, _lineNumber); - else - //Insert into the document - document.ParserPutValue(key, value, _lineNumber); + if (_currentTable != null) + //Insert into current table + _currentTable.ParserPutValue(key, value, _lineNumber); + else + //Insert into the document + document.ParserPutValue(key, value, _lineNumber); - //Read up until the end of the line, ignoring any comments or whitespace - reader.SkipWhitespace(); + //Read up until the end of the line, ignoring any comments or whitespace + reader.SkipWhitespace(); - //Ensure we have a newline - reader.SkipPotentialCarriageReturn(); - if (!reader.ExpectAndConsume('\n') && reader.TryPeek(out var shouldHaveBeenLf)) - //Not EOF and found a non-newline char - throw new TomlMissingNewlineException(_lineNumber, (char) shouldHaveBeenLf); + //Ensure we have a newline + reader.SkipPotentialCarriageReturn(); + if (!reader.ExpectAndConsume('\n') && reader.TryPeek(out var shouldHaveBeenLf)) + //Not EOF and found a non-newline char + throw new TomlMissingNewlineException(_lineNumber, (char) shouldHaveBeenLf); - _lineNumber++; //We've consumed a newline, move to the next line number. - } + _lineNumber++; //We've consumed a newline, move to the next line number. + } - document.TrailingComment = lastPrecedingComment; + document.TrailingComment = lastPrecedingComment; - return document; - } - catch (Exception e) when (e is not TomlException) - { - throw new TomlInternalException(_lineNumber, e); - } + return document; } - - private void ReadKeyValuePair(TomletStringReader reader, out string key, out TomlValue value) + catch (Exception e) when (e is not TomlException) { - //Read the key - key = ReadKey(reader); - - //Consume the equals sign, potentially with whitespace either side. - reader.SkipWhitespace(); - if (!reader.ExpectAndConsume('=')) - { - if (reader.TryPeek(out var shouldHaveBeenEquals)) - throw new TomlMissingEqualsException(_lineNumber, (char) shouldHaveBeenEquals); + throw new TomlInternalException(_lineNumber, e); + } + } - throw new TomlEndOfFileException(_lineNumber); - } + private void ReadKeyValuePair(TomletStringReader reader, out string key, out TomlValue value) + { + //Read the key + key = ReadKey(reader); - reader.SkipWhitespace(); + //Consume the equals sign, potentially with whitespace either side. + reader.SkipWhitespace(); + if (!reader.ExpectAndConsume('=')) + { + if (reader.TryPeek(out var shouldHaveBeenEquals)) + throw new TomlMissingEqualsException(_lineNumber, (char) shouldHaveBeenEquals); - //Read the value - value = ReadValue(reader); + throw new TomlEndOfFileException(_lineNumber); } - private string ReadKey(TomletStringReader reader) - { - reader.SkipWhitespace(); + reader.SkipWhitespace(); - if (!reader.TryPeek(out var nextChar)) - return ""; + //Read the value + value = ReadValue(reader); + } - if (nextChar.IsEquals()) - throw new NoTomlKeyException(_lineNumber); + private string ReadKey(TomletStringReader reader) + { + reader.SkipWhitespace(); - //Read a key - reader.SkipWhitespace(); + if (!reader.TryPeek(out var nextChar)) + return ""; - string key; - if (nextChar.IsDoubleQuote()) - { - //Read double-quoted key - reader.Read(); - if (reader.TryPeek(out var maybeSecondDoubleQuote) && maybeSecondDoubleQuote.IsDoubleQuote()) - { - reader.Read(); //Consume second double quote. + if (nextChar.IsEquals()) + throw new NoTomlKeyException(_lineNumber); - //Check for third quote => invalid key - //Else => empty key - if (reader.TryPeek(out var maybeThirdDoubleQuote) && maybeThirdDoubleQuote.IsDoubleQuote()) - throw new TomlTripleQuotedKeyException(_lineNumber); + //Read a key + reader.SkipWhitespace(); - return string.Empty; - } + string key; + if (nextChar.IsDoubleQuote()) + { + //Read double-quoted key + reader.Read(); + if (reader.TryPeek(out var maybeSecondDoubleQuote) && maybeSecondDoubleQuote.IsDoubleQuote()) + { + reader.Read(); //Consume second double quote. - //We delegate to the dedicated string reading function here because a double-quoted key can contain everything a double-quoted string can. - key = '"' + ReadSingleLineBasicString(reader, false).StringValue + '"'; + //Check for third quote => invalid key + //Else => empty key + if (reader.TryPeek(out var maybeThirdDoubleQuote) && maybeThirdDoubleQuote.IsDoubleQuote()) + throw new TomlTripleQuotedKeyException(_lineNumber); - if (!reader.ExpectAndConsume('"')) - throw new UnterminatedTomlKeyException(_lineNumber); + return string.Empty; } - else if (nextChar.IsSingleQuote()) - { - reader.Read(); //Consume opening quote. - //Read single-quoted key - key = "'" + ReadSingleLineLiteralString(reader, false).StringValue + "'"; - if (!reader.ExpectAndConsume('\'')) - throw new UnterminatedTomlKeyException(_lineNumber); - } - else - //Read unquoted key - key = ReadKeyInternal(reader, keyChar => keyChar.IsEquals() || keyChar.IsHashSign()); + //We delegate to the dedicated string reading function here because a double-quoted key can contain everything a double-quoted string can. + key = '"' + ReadSingleLineBasicString(reader, false).StringValue + '"'; - key = key.Replace("\\n", "\n") - .Replace("\\t", "\t"); + if (!reader.ExpectAndConsume('"')) + throw new UnterminatedTomlKeyException(_lineNumber); + } + else if (nextChar.IsSingleQuote()) + { + reader.Read(); //Consume opening quote. - return key; + //Read single-quoted key + key = "'" + ReadSingleLineLiteralString(reader, false).StringValue + "'"; + if (!reader.ExpectAndConsume('\'')) + throw new UnterminatedTomlKeyException(_lineNumber); } + else + //Read unquoted key + key = ReadKeyInternal(reader, keyChar => keyChar.IsEquals() || keyChar.IsHashSign()); - private string ReadKeyInternal(TomletStringReader reader, Func charSignalsEndOfKey) - { - var parts = new List(); + key = key.Replace("\\n", "\n") + .Replace("\\t", "\t"); - //Parts loop - while (reader.TryPeek(out var nextChar)) - { - if (charSignalsEndOfKey(nextChar)) - return string.Join(".", parts.ToArray()); + return key; + } - if (nextChar.IsPeriod()) - throw new TomlDoubleDottedKeyException(_lineNumber); + private string ReadKeyInternal(TomletStringReader reader, Func charSignalsEndOfKey) + { + var parts = new List(); - var thisPart = new StringBuilder(); - //Part loop - while (reader.TryPeek(out nextChar)) - { - nextChar.EnsureLegalChar(_lineNumber); - - var numLeadingWhitespace = reader.SkipWhitespace(); - reader.TryPeek(out var charAfterWhitespace); - if (charAfterWhitespace.IsPeriod()) - { - //Whitespace is permitted in keys only around periods - parts.Add(thisPart.ToString()); //Add this part + //Parts loop + while (reader.TryPeek(out var nextChar)) + { + if (charSignalsEndOfKey(nextChar)) + return string.Join(".", parts.ToArray()); - //Consume period and any trailing whitespace - reader.ExpectAndConsume('.'); - reader.SkipWhitespace(); - break; //End of part, move to next - } + if (nextChar.IsPeriod()) + throw new TomlDoubleDottedKeyException(_lineNumber); - if (numLeadingWhitespace > 0 && charSignalsEndOfKey(charAfterWhitespace)) - { - //Add this part to the list of parts and break out of the loop, without consuming the char (it'll be picked up by the outer loop) - parts.Add(thisPart.ToString()); - break; - } + var thisPart = new StringBuilder(); + //Part loop + while (reader.TryPeek(out nextChar)) + { + nextChar.EnsureLegalChar(_lineNumber); + + var numLeadingWhitespace = reader.SkipWhitespace(); + reader.TryPeek(out var charAfterWhitespace); + if (charAfterWhitespace.IsPeriod()) + { + //Whitespace is permitted in keys only around periods + parts.Add(thisPart.ToString()); //Add this part - //Un-skip the whitespace - reader.Backtrack(numLeadingWhitespace); + //Consume period and any trailing whitespace + reader.ExpectAndConsume('.'); + reader.SkipWhitespace(); + break; //End of part, move to next + } - //NextChar is still the whitespace itself - if (charSignalsEndOfKey(nextChar)) - { - //Add this part to the list of parts and break out of the loop, without consuming the char (it'll be picked up by the outer loop) - parts.Add(thisPart.ToString()); - break; - } + if (numLeadingWhitespace > 0 && charSignalsEndOfKey(charAfterWhitespace)) + { + //Add this part to the list of parts and break out of the loop, without consuming the char (it'll be picked up by the outer loop) + parts.Add(thisPart.ToString()); + break; + } - if (numLeadingWhitespace > 0) - //Whitespace is not allowed outside of the area immediately around a period in a dotted key - throw new TomlWhitespaceInKeyException(_lineNumber); + //Un-skip the whitespace + reader.Backtrack(numLeadingWhitespace); - //Append this char to the part - thisPart.Append((char) reader.Read()); + //NextChar is still the whitespace itself + if (charSignalsEndOfKey(nextChar)) + { + //Add this part to the list of parts and break out of the loop, without consuming the char (it'll be picked up by the outer loop) + parts.Add(thisPart.ToString()); + break; } + + if (numLeadingWhitespace > 0) + //Whitespace is not allowed outside of the area immediately around a period in a dotted key + throw new TomlWhitespaceInKeyException(_lineNumber); + + //Append this char to the part + thisPart.Append((char) reader.Read()); } + } + + throw new TomlEndOfFileException(_lineNumber); + } + private TomlValue ReadValue(TomletStringReader reader) + { + if (!reader.TryPeek(out var startOfValue)) throw new TomlEndOfFileException(_lineNumber); - } - private TomlValue ReadValue(TomletStringReader reader) + TomlValue value; + switch (startOfValue) { - if (!reader.TryPeek(out var startOfValue)) - throw new TomlEndOfFileException(_lineNumber); + case '[': + //Array + value = ReadArray(reader); + break; + case '{': + //Inline table + value = ReadInlineTable(reader); + break; + case '"': + case '\'': + //Basic or Literal String, maybe multiline + var startQuote = reader.Read(); + var maybeSecondQuote = reader.Peek(); + if (maybeSecondQuote != startQuote) + //Second char is not first, this is a single-line string. + value = startQuote.IsSingleQuote() ? ReadSingleLineLiteralString(reader) : ReadSingleLineBasicString(reader); + else + { + reader.Read(); //Consume second char - TomlValue value; - switch (startOfValue) - { - case '[': - //Array - value = ReadArray(reader); - break; - case '{': - //Inline table - value = ReadInlineTable(reader); - break; - case '"': - case '\'': - //Basic or Literal String, maybe multiline - var startQuote = reader.Read(); - var maybeSecondQuote = reader.Peek(); - if (maybeSecondQuote != startQuote) - //Second char is not first, this is a single-line string. - value = startQuote.IsSingleQuote() ? ReadSingleLineLiteralString(reader) : ReadSingleLineBasicString(reader); - else + //Check the third char. If it's another quote, we have a multiline string. If it's whitespace, a newline, part of an inline array, or a #, we have an empty string. + //Anything else is an error. + var maybeThirdQuote = reader.Peek(); + if (maybeThirdQuote == startQuote) { - reader.Read(); //Consume second char - - //Check the third char. If it's another quote, we have a multiline string. If it's whitespace, a newline, part of an inline array, or a #, we have an empty string. - //Anything else is an error. - var maybeThirdQuote = reader.Peek(); - if (maybeThirdQuote == startQuote) - { - reader.Read(); //Consume the third opening quote, for simplicity's sake. - value = startQuote.IsSingleQuote() ? ReadMultiLineLiteralString(reader) : ReadMultiLineBasicString(reader); - } - else if (maybeThirdQuote.IsWhitespace() || maybeThirdQuote.IsNewline() || maybeThirdQuote.IsHashSign() || maybeThirdQuote.IsComma() || maybeThirdQuote.IsEndOfArrayChar() || maybeThirdQuote == -1) - { - value = TomlString.Empty; - } - else - { - throw new TomlStringException(_lineNumber); - } + reader.Read(); //Consume the third opening quote, for simplicity's sake. + value = startQuote.IsSingleQuote() ? ReadMultiLineLiteralString(reader) : ReadMultiLineBasicString(reader); + } + else if (maybeThirdQuote.IsWhitespace() || maybeThirdQuote.IsNewline() || maybeThirdQuote.IsHashSign() || maybeThirdQuote.IsComma() || maybeThirdQuote.IsEndOfArrayChar() || maybeThirdQuote == -1) + { + value = TomlString.Empty; } - - break; - case '+': - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case 'i': - case 'n': - //I kind of hate that but it's probably fast. - //Number. Maybe floating-point. - //i and n indicate special floating point values (inf and nan). - - //Read a string, stopping if we hit an equals, whitespace, newline, or comment. - var stringValue = reader.ReadWhile(valueChar => !valueChar.IsEquals() && !valueChar.IsNewline() && !valueChar.IsHashSign() && !valueChar.IsComma() && !valueChar.IsEndOfArrayChar() && !valueChar.IsEndOfInlineObjectChar()) - .ToLowerInvariant().Trim(); - - if (stringValue.Contains(':') || stringValue.Contains('t') || stringValue.Contains(' ') || stringValue.Contains('z')) - value = TomlDateTimeUtils.ParseDateString(stringValue, _lineNumber) ?? throw new InvalidTomlDateTimeException(_lineNumber, stringValue); - else if (stringValue.Contains('.') || (stringValue.Contains('e') && !stringValue.StartsWith("0x")) || stringValue.Contains('n') || stringValue.Contains('i')) - //Try parse as a double, then fall back to a date/time. - value = TomlDouble.Parse(stringValue) ?? TomlDateTimeUtils.ParseDateString(stringValue, _lineNumber) ?? throw new InvalidTomlNumberException(_lineNumber, stringValue); else - //Try parse as a long, then fall back to a date/time. - value = TomlLong.Parse(stringValue) ?? TomlDateTimeUtils.ParseDateString(stringValue, _lineNumber) ?? throw new InvalidTomlNumberException(_lineNumber, stringValue); + { + throw new TomlStringException(_lineNumber); + } + } - break; - case 't': - { - //Either "true" or an error - var charsRead = reader.ReadChars(4); + break; + case '+': + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'i': + case 'n': + //I kind of hate that but it's probably fast. + //Number. Maybe floating-point. + //i and n indicate special floating point values (inf and nan). + + //Read a string, stopping if we hit an equals, whitespace, newline, or comment. + var stringValue = reader.ReadWhile(valueChar => !valueChar.IsEquals() && !valueChar.IsNewline() && !valueChar.IsHashSign() && !valueChar.IsComma() && !valueChar.IsEndOfArrayChar() && !valueChar.IsEndOfInlineObjectChar()) + .ToLowerInvariant().Trim(); + + if (stringValue.Contains(':') || stringValue.Contains('t') || stringValue.Contains(' ') || stringValue.Contains('z')) + value = TomlDateTimeUtils.ParseDateString(stringValue, _lineNumber) ?? throw new InvalidTomlDateTimeException(_lineNumber, stringValue); + else if (stringValue.Contains('.') || (stringValue.Contains('e') && !stringValue.StartsWith("0x")) || stringValue.Contains('n') || stringValue.Contains('i')) + //Try parse as a double, then fall back to a date/time. + value = TomlDouble.Parse(stringValue) ?? TomlDateTimeUtils.ParseDateString(stringValue, _lineNumber) ?? throw new InvalidTomlNumberException(_lineNumber, stringValue); + else + //Try parse as a long, then fall back to a date/time. + value = TomlLong.Parse(stringValue) ?? TomlDateTimeUtils.ParseDateString(stringValue, _lineNumber) ?? throw new InvalidTomlNumberException(_lineNumber, stringValue); - if (!TrueChars.SequenceEqual(charsRead)) - throw new TomlInvalidValueException(_lineNumber, (char) startOfValue); + break; + case 't': + { + //Either "true" or an error + var charsRead = reader.ReadChars(4); - value = TomlBoolean.True; - break; - } - case 'f': - { - //Either "false" or an error - var charsRead = reader.ReadChars(5); + if (!TrueChars.SequenceEqual(charsRead)) + throw new TomlInvalidValueException(_lineNumber, (char) startOfValue); - if (!FalseChars.SequenceEqual(charsRead)) - throw new TomlInvalidValueException(_lineNumber, (char) startOfValue); + value = TomlBoolean.True; + break; + } + case 'f': + { + //Either "false" or an error + var charsRead = reader.ReadChars(5); - value = TomlBoolean.False; - break; - } - default: + if (!FalseChars.SequenceEqual(charsRead)) throw new TomlInvalidValueException(_lineNumber, (char) startOfValue); + + value = TomlBoolean.False; + break; } + default: + throw new TomlInvalidValueException(_lineNumber, (char) startOfValue); + } - reader.SkipWhitespace(); - value.Comments.InlineComment = ReadAnyPotentialInlineComment(reader); + reader.SkipWhitespace(); + value.Comments.InlineComment = ReadAnyPotentialInlineComment(reader); - return value; - } + return value; + } + + private TomlValue ReadSingleLineBasicString(TomletStringReader reader, bool consumeClosingQuote = true) + { + //No simple read here, we have to accomodate escaped double quotes. + var content = new StringBuilder(); - private TomlValue ReadSingleLineBasicString(TomletStringReader reader, bool consumeClosingQuote = true) + var escapeMode = false; + var fourDigitUnicodeMode = false; + var eightDigitUnicodeMode = false; + + var unicodeStringBuilder = new StringBuilder(); + while (reader.TryPeek(out var nextChar)) { - //No simple read here, we have to accomodate escaped double quotes. - var content = new StringBuilder(); + nextChar.EnsureLegalChar(_lineNumber); + if (nextChar == '"' && !escapeMode) + break; - var escapeMode = false; - var fourDigitUnicodeMode = false; - var eightDigitUnicodeMode = false; + reader.Read(); //Consume the next char - var unicodeStringBuilder = new StringBuilder(); - while (reader.TryPeek(out var nextChar)) + if (nextChar == '\\' && !escapeMode) { - nextChar.EnsureLegalChar(_lineNumber); - if (nextChar == '"' && !escapeMode) - break; - - reader.Read(); //Consume the next char + escapeMode = true; + continue; //Don't append + } - if (nextChar == '\\' && !escapeMode) - { - escapeMode = true; - continue; //Don't append - } + if (escapeMode) + { + escapeMode = false; + var toAppend = HandleEscapedChar(nextChar, out fourDigitUnicodeMode, out eightDigitUnicodeMode); - if (escapeMode) - { - escapeMode = false; - var toAppend = HandleEscapedChar(nextChar, out fourDigitUnicodeMode, out eightDigitUnicodeMode); + if (toAppend.HasValue) + content.Append(toAppend.Value); + continue; + } - if (toAppend.HasValue) - content.Append(toAppend.Value); - continue; - } + if (fourDigitUnicodeMode || eightDigitUnicodeMode) + { + //Handle \u1234 and \U12345678 + unicodeStringBuilder.Append((char) nextChar); - if (fourDigitUnicodeMode || eightDigitUnicodeMode) + if (fourDigitUnicodeMode && unicodeStringBuilder.Length == 4 || eightDigitUnicodeMode && unicodeStringBuilder.Length == 8) { - //Handle \u1234 and \U12345678 - unicodeStringBuilder.Append((char) nextChar); + var unicodeString = unicodeStringBuilder.ToString(); - if (fourDigitUnicodeMode && unicodeStringBuilder.Length == 4 || eightDigitUnicodeMode && unicodeStringBuilder.Length == 8) - { - var unicodeString = unicodeStringBuilder.ToString(); - - content.Append(DecipherUnicodeEscapeSequence(unicodeString, fourDigitUnicodeMode)); + content.Append(DecipherUnicodeEscapeSequence(unicodeString, fourDigitUnicodeMode)); - fourDigitUnicodeMode = false; - eightDigitUnicodeMode = false; - unicodeStringBuilder = new StringBuilder(); - } - - continue; + fourDigitUnicodeMode = false; + eightDigitUnicodeMode = false; + unicodeStringBuilder = new StringBuilder(); } - if (nextChar.IsNewline()) - throw new UnterminatedTomlStringException(_lineNumber); - - content.Append((char) nextChar); + continue; } - if (consumeClosingQuote) - { - if (!reader.ExpectAndConsume('"')) - throw new UnterminatedTomlStringException(_lineNumber); - } + if (nextChar.IsNewline()) + throw new UnterminatedTomlStringException(_lineNumber); - return new TomlString(content.ToString()); + content.Append((char) nextChar); } - private string DecipherUnicodeEscapeSequence(string unicodeString, bool fourDigitMode) + if (consumeClosingQuote) { - if (unicodeString.Any(c => !c.IsHexDigit())) - throw new InvalidTomlEscapeException(_lineNumber, $"\\{(fourDigitMode ? 'u' : 'U')}{unicodeString}"); + if (!reader.ExpectAndConsume('"')) + throw new UnterminatedTomlStringException(_lineNumber); + } - if (fourDigitMode) - { - //16-bit char - var decodedChar = short.Parse(unicodeString, NumberStyles.HexNumber); - return ((char) decodedChar).ToString(); - } + return new TomlString(content.ToString()); + } - //32-bit char - var decodedChars = int.Parse(unicodeString, NumberStyles.HexNumber); - return char.ConvertFromUtf32(decodedChars); - } + private string DecipherUnicodeEscapeSequence(string unicodeString, bool fourDigitMode) + { + if (unicodeString.Any(c => !c.IsHexDigit())) + throw new InvalidTomlEscapeException(_lineNumber, $"\\{(fourDigitMode ? 'u' : 'U')}{unicodeString}"); - private char? HandleEscapedChar(int escapedChar, out bool fourDigitUnicodeMode, out bool eightDigitUnicodeMode, bool allowNewline = false) + if (fourDigitMode) { - eightDigitUnicodeMode = false; - fourDigitUnicodeMode = false; + //16-bit char + var decodedChar = short.Parse(unicodeString, NumberStyles.HexNumber); + return ((char) decodedChar).ToString(); + } - char toAppend; - switch (escapedChar) - { - case 'b': - toAppend = '\b'; - break; - case 't': - toAppend = '\t'; - break; - case 'n': - toAppend = '\n'; - break; - case 'f': - toAppend = '\f'; - break; - case 'r': - toAppend = '\r'; - break; - case '"': - toAppend = '"'; - break; - case '\\': - toAppend = '\\'; - break; - case 'u': - fourDigitUnicodeMode = true; - return null; - case 'U': - eightDigitUnicodeMode = true; - return null; - default: - if (allowNewline && escapedChar.IsNewline()) - return null; - throw new InvalidTomlEscapeException(_lineNumber, $"\\{escapedChar}"); - } + //32-bit char + var decodedChars = int.Parse(unicodeString, NumberStyles.HexNumber); + return char.ConvertFromUtf32(decodedChars); + } - return toAppend; - } + private char? HandleEscapedChar(int escapedChar, out bool fourDigitUnicodeMode, out bool eightDigitUnicodeMode, bool allowNewline = false) + { + eightDigitUnicodeMode = false; + fourDigitUnicodeMode = false; - private TomlValue ReadSingleLineLiteralString(TomletStringReader reader, bool consumeClosingQuote = true) + char toAppend; + switch (escapedChar) { - //Literally (hah) just read until a single-quote - var stringContent = reader.ReadWhile(valueChar => !valueChar.IsSingleQuote() && !valueChar.IsNewline()); + case 'b': + toAppend = '\b'; + break; + case 't': + toAppend = '\t'; + break; + case 'n': + toAppend = '\n'; + break; + case 'f': + toAppend = '\f'; + break; + case 'r': + toAppend = '\r'; + break; + case '"': + toAppend = '"'; + break; + case '\\': + toAppend = '\\'; + break; + case 'u': + fourDigitUnicodeMode = true; + return null; + case 'U': + eightDigitUnicodeMode = true; + return null; + default: + if (allowNewline && escapedChar.IsNewline()) + return null; + throw new InvalidTomlEscapeException(_lineNumber, $"\\{escapedChar}"); + } + + return toAppend; + } + + private TomlValue ReadSingleLineLiteralString(TomletStringReader reader, bool consumeClosingQuote = true) + { + //Literally (hah) just read until a single-quote + var stringContent = reader.ReadWhile(valueChar => !valueChar.IsSingleQuote() && !valueChar.IsNewline()); - foreach (var i in stringContent.Select(c => (int) c)) - i.EnsureLegalChar(_lineNumber); + foreach (var i in stringContent.Select(c => (int) c)) + i.EnsureLegalChar(_lineNumber); - if (!reader.TryPeek(out var terminatingChar)) - //Unexpected EOF - throw new TomlEndOfFileException(_lineNumber); + if (!reader.TryPeek(out var terminatingChar)) + //Unexpected EOF + throw new TomlEndOfFileException(_lineNumber); - if (!terminatingChar.IsSingleQuote()) - throw new UnterminatedTomlStringException(_lineNumber); + if (!terminatingChar.IsSingleQuote()) + throw new UnterminatedTomlStringException(_lineNumber); - if (consumeClosingQuote) - reader.Read(); //Consume terminating quote. + if (consumeClosingQuote) + reader.Read(); //Consume terminating quote. - return new TomlString(stringContent); - } + return new TomlString(stringContent); + } - private TomlValue ReadMultiLineLiteralString(TomletStringReader reader) + private TomlValue ReadMultiLineLiteralString(TomletStringReader reader) + { + var content = new StringBuilder(); + //Ignore any first-line newlines + _lineNumber += reader.SkipAnyNewline(); + while (reader.TryPeek(out _)) { - var content = new StringBuilder(); - //Ignore any first-line newlines - _lineNumber += reader.SkipAnyNewline(); - while (reader.TryPeek(out _)) + var nextChar = reader.Read(); + nextChar.EnsureLegalChar(_lineNumber); + + if (!nextChar.IsSingleQuote()) { - var nextChar = reader.Read(); - nextChar.EnsureLegalChar(_lineNumber); + content.Append((char) nextChar); - if (!nextChar.IsSingleQuote()) - { - content.Append((char) nextChar); + if (nextChar == '\n') + _lineNumber++; //We've wrapped to a new line. - if (nextChar == '\n') - _lineNumber++; //We've wrapped to a new line. + continue; + } - continue; - } + //We have a single quote. + //Is it alone? if so, just continue. + if (!reader.TryPeek(out var potentialSecondQuote) || !potentialSecondQuote.IsSingleQuote()) + { + content.Append('\''); + continue; + } - //We have a single quote. - //Is it alone? if so, just continue. - if (!reader.TryPeek(out var potentialSecondQuote) || !potentialSecondQuote.IsSingleQuote()) - { - content.Append('\''); - continue; - } + //We have two quotes in a row. Consume the second one + reader.Read(); - //We have two quotes in a row. Consume the second one - reader.Read(); + //Do we have three? + if (!reader.TryPeek(out var potentialThirdQuote) || !potentialThirdQuote.IsSingleQuote()) + { + content.Append('\''); + content.Append('\''); + continue; + } - //Do we have three? - if (!reader.TryPeek(out var potentialThirdQuote) || !potentialThirdQuote.IsSingleQuote()) - { - content.Append('\''); - content.Append('\''); - continue; - } + //Ok we have at least three quotes. Consume the third. + reader.Read(); - //Ok we have at least three quotes. Consume the third. - reader.Read(); + if (!reader.TryPeek(out var afterThirdQuote) || !afterThirdQuote.IsSingleQuote()) + //And ONLY three quotes. End of literal. + break; - if (!reader.TryPeek(out var afterThirdQuote) || !afterThirdQuote.IsSingleQuote()) - //And ONLY three quotes. End of literal. - break; + //We're at 4 single quotes back-to-back at this point, and the max is 5. I'm just going to do this without a loop because it's probably actually less code. + //Consume the fourth. + reader.Read(); + //And we have to append one single quote to our string. + content.Append('\''); - //We're at 4 single quotes back-to-back at this point, and the max is 5. I'm just going to do this without a loop because it's probably actually less code. - //Consume the fourth. - reader.Read(); - //And we have to append one single quote to our string. - content.Append('\''); + //Check for a 5th. + if (!reader.TryPeek(out var potentialFifthQuote) || !potentialFifthQuote.IsSingleQuote()) + //Four in total, so we bail out here. + break; - //Check for a 5th. - if (!reader.TryPeek(out var potentialFifthQuote) || !potentialFifthQuote.IsSingleQuote()) - //Four in total, so we bail out here. - break; + //We have a 5th. Consume it. + reader.Read(); + //And append to output + content.Append('\''); - //We have a 5th. Consume it. - reader.Read(); - //And append to output - content.Append('\''); + //Check for sixth + if (!reader.TryPeek(out var potentialSixthQuote) || !potentialSixthQuote.IsSingleQuote()) + //Five in total, so we bail out here. + break; - //Check for sixth - if (!reader.TryPeek(out var potentialSixthQuote) || !potentialSixthQuote.IsSingleQuote()) - //Five in total, so we bail out here. - break; + //We have a sixth. This is a syntax error. + throw new TripleQuoteInTomlMultilineLiteralException(_lineNumber); + } - //We have a sixth. This is a syntax error. - throw new TripleQuoteInTomlMultilineLiteralException(_lineNumber); - } + return new TomlString(content.ToString()); + } - return new TomlString(content.ToString()); - } + private TomlValue ReadMultiLineBasicString(TomletStringReader reader) + { + var content = new StringBuilder(); - private TomlValue ReadMultiLineBasicString(TomletStringReader reader) - { - var content = new StringBuilder(); + var escapeMode = false; + var fourDigitUnicodeMode = false; + var eightDigitUnicodeMode = false; - var escapeMode = false; - var fourDigitUnicodeMode = false; - var eightDigitUnicodeMode = false; + var unicodeStringBuilder = new StringBuilder(); - var unicodeStringBuilder = new StringBuilder(); + //Leading newlines are ignored + _lineNumber += reader.SkipAnyNewline(); - //Leading newlines are ignored - _lineNumber += reader.SkipAnyNewline(); + while (reader.TryPeek(out _)) + { + var nextChar = reader.Read(); + nextChar.EnsureLegalChar(_lineNumber); - while (reader.TryPeek(out _)) + if (nextChar == '\\' && !escapeMode) { - var nextChar = reader.Read(); - nextChar.EnsureLegalChar(_lineNumber); + escapeMode = true; + continue; //Don't append + } - if (nextChar == '\\' && !escapeMode) - { - escapeMode = true; - continue; //Don't append - } + if (escapeMode) + { + escapeMode = false; + var toAppend = HandleEscapedChar(nextChar, out fourDigitUnicodeMode, out eightDigitUnicodeMode, true); - if (escapeMode) + if (toAppend.HasValue) + content.Append(toAppend.Value); + else if (nextChar.IsNewline()) { - escapeMode = false; - var toAppend = HandleEscapedChar(nextChar, out fourDigitUnicodeMode, out eightDigitUnicodeMode, true); - - if (toAppend.HasValue) - content.Append(toAppend.Value); - else if (nextChar.IsNewline()) - { - //Ensure we've fully consumed the newline - if (nextChar == '\r' && !reader.ExpectAndConsume('\n')) - throw new Exception($"Found a CR without an LF on line {_lineNumber}"); + //Ensure we've fully consumed the newline + if (nextChar == '\r' && !reader.ExpectAndConsume('\n')) + throw new Exception($"Found a CR without an LF on line {_lineNumber}"); - //Increment line number - _lineNumber++; + //Increment line number + _lineNumber++; - //Escaped newline indicates we skip this newline and any whitespace at the start of the next line - reader.SkipAnyNewlineOrWhitespace(); - } - - continue; + //Escaped newline indicates we skip this newline and any whitespace at the start of the next line + reader.SkipAnyNewlineOrWhitespace(); } - if (fourDigitUnicodeMode || eightDigitUnicodeMode) - { - //Handle \u1234 and \U12345678 - unicodeStringBuilder.Append((char) nextChar); + continue; + } - if (fourDigitUnicodeMode && unicodeStringBuilder.Length == 4 || eightDigitUnicodeMode && unicodeStringBuilder.Length == 8) - { - var unicodeString = unicodeStringBuilder.ToString(); + if (fourDigitUnicodeMode || eightDigitUnicodeMode) + { + //Handle \u1234 and \U12345678 + unicodeStringBuilder.Append((char) nextChar); - content.Append(DecipherUnicodeEscapeSequence(unicodeString, fourDigitUnicodeMode)); + if (fourDigitUnicodeMode && unicodeStringBuilder.Length == 4 || eightDigitUnicodeMode && unicodeStringBuilder.Length == 8) + { + var unicodeString = unicodeStringBuilder.ToString(); - fourDigitUnicodeMode = false; - eightDigitUnicodeMode = false; - unicodeStringBuilder = new StringBuilder(); - } + content.Append(DecipherUnicodeEscapeSequence(unicodeString, fourDigitUnicodeMode)); - continue; + fourDigitUnicodeMode = false; + eightDigitUnicodeMode = false; + unicodeStringBuilder = new StringBuilder(); } - if (!nextChar.IsDoubleQuote()) - { - if (nextChar == '\n') - _lineNumber++; + continue; + } - content.Append((char) nextChar); - continue; - } + if (!nextChar.IsDoubleQuote()) + { + if (nextChar == '\n') + _lineNumber++; - //Like above, check for up to 6 quotes. + content.Append((char) nextChar); + continue; + } - //We have a double quote. - //Is it alone? if so, just continue. - if (!reader.TryPeek(out var potentialSecondQuote) || !potentialSecondQuote.IsDoubleQuote()) - { - content.Append('"'); - continue; - } + //Like above, check for up to 6 quotes. - //We have two quotes in a row. Consume the second one - reader.Read(); + //We have a double quote. + //Is it alone? if so, just continue. + if (!reader.TryPeek(out var potentialSecondQuote) || !potentialSecondQuote.IsDoubleQuote()) + { + content.Append('"'); + continue; + } - //Do we have three? - if (!reader.TryPeek(out var potentialThirdQuote) || !potentialThirdQuote.IsDoubleQuote()) - { - content.Append('"'); - content.Append('"'); - continue; - } + //We have two quotes in a row. Consume the second one + reader.Read(); - //Ok we have at least three quotes. Consume the third. - reader.Read(); + //Do we have three? + if (!reader.TryPeek(out var potentialThirdQuote) || !potentialThirdQuote.IsDoubleQuote()) + { + content.Append('"'); + content.Append('"'); + continue; + } - if (!reader.TryPeek(out var afterThirdQuote) || !afterThirdQuote.IsDoubleQuote()) - //And ONLY three quotes. End of literal. - break; + //Ok we have at least three quotes. Consume the third. + reader.Read(); - //Like above, just going to bruteforce this out instead of writing a loop. - //Consume the fourth. - reader.Read(); - //And we have to append one double quote to our string. - content.Append('"'); + if (!reader.TryPeek(out var afterThirdQuote) || !afterThirdQuote.IsDoubleQuote()) + //And ONLY three quotes. End of literal. + break; - //Check for a 5th. - if (!reader.TryPeek(out var potentialFifthQuote) || !potentialFifthQuote.IsDoubleQuote()) - //Four in total, so we bail out here. - break; + //Like above, just going to bruteforce this out instead of writing a loop. + //Consume the fourth. + reader.Read(); + //And we have to append one double quote to our string. + content.Append('"'); - //We have a 5th. Consume it. - reader.Read(); - //And append to output - content.Append('"'); + //Check for a 5th. + if (!reader.TryPeek(out var potentialFifthQuote) || !potentialFifthQuote.IsDoubleQuote()) + //Four in total, so we bail out here. + break; - //Check for sixth - if (!reader.TryPeek(out var potentialSixthQuote) || !potentialSixthQuote.IsDoubleQuote()) - //Five in total, so we bail out here. - break; + //We have a 5th. Consume it. + reader.Read(); + //And append to output + content.Append('"'); - //We have a sixth. This is a syntax error. - throw new TripleQuoteInTomlMultilineSimpleStringException(_lineNumber); - } + //Check for sixth + if (!reader.TryPeek(out var potentialSixthQuote) || !potentialSixthQuote.IsDoubleQuote()) + //Five in total, so we bail out here. + break; - return new TomlString(content.ToString()); + //We have a sixth. This is a syntax error. + throw new TripleQuoteInTomlMultilineSimpleStringException(_lineNumber); } - private TomlArray ReadArray(TomletStringReader reader) - { - //Consume the opening bracket - if (!reader.ExpectAndConsume('[')) - throw new ArgumentException("Internal Tomlet Bug: ReadArray called and first char is not a ["); - - //Move to the first value - _lineNumber += reader.SkipAnyCommentNewlineWhitespaceEtc(); + return new TomlString(content.ToString()); + } - var result = new TomlArray(); + private TomlArray ReadArray(TomletStringReader reader) + { + //Consume the opening bracket + if (!reader.ExpectAndConsume('[')) + throw new ArgumentException("Internal Tomlet Bug: ReadArray called and first char is not a ["); - while (reader.TryPeek(out _)) - { - //Skip any empty lines - _lineNumber += reader.SkipAnyCommentNewlineWhitespaceEtc(); + //Move to the first value + _lineNumber += reader.SkipAnyCommentNewlineWhitespaceEtc(); - if (!reader.TryPeek(out var nextChar)) - throw new TomlEndOfFileException(_lineNumber); + var result = new TomlArray(); - //Check for end of array here (helps with trailing commas, which are legal) - if (nextChar.IsEndOfArrayChar()) - break; + while (reader.TryPeek(out _)) + { + //Skip any empty lines + _lineNumber += reader.SkipAnyCommentNewlineWhitespaceEtc(); - //Read a value - result.ArrayValues.Add(ReadValue(reader)); + if (!reader.TryPeek(out var nextChar)) + throw new TomlEndOfFileException(_lineNumber); - //Skip any whitespace or newlines, NOT comments - that would be a syntax error - _lineNumber += reader.SkipAnyNewlineOrWhitespace(); + //Check for end of array here (helps with trailing commas, which are legal) + if (nextChar.IsEndOfArrayChar()) + break; - if (!reader.TryPeek(out var postValueChar)) - throw new TomlEndOfFileException(_lineNumber); + //Read a value + result.ArrayValues.Add(ReadValue(reader)); - if (postValueChar.IsEndOfArrayChar()) - break; //end of array + //Skip any whitespace or newlines, NOT comments - that would be a syntax error + _lineNumber += reader.SkipAnyNewlineOrWhitespace(); - if (!postValueChar.IsComma()) - throw new TomlArraySyntaxException(_lineNumber, (char) postValueChar); + if (!reader.TryPeek(out var postValueChar)) + throw new TomlEndOfFileException(_lineNumber); - reader.ExpectAndConsume(','); //We've already verified we have one. - } + if (postValueChar.IsEndOfArrayChar()) + break; //end of array - reader.ExpectAndConsume(']'); + if (!postValueChar.IsComma()) + throw new TomlArraySyntaxException(_lineNumber, (char) postValueChar); - return result; + reader.ExpectAndConsume(','); //We've already verified we have one. } - private TomlTable ReadInlineTable(TomletStringReader reader) - { - //Consume the opening brace - if (!reader.ExpectAndConsume('{')) - throw new ArgumentException("Internal Tomlet Bug: ReadInlineTable called and first char is not a {"); + reader.ExpectAndConsume(']'); - //Move to the first key - _lineNumber += reader.SkipAnyCommentNewlineWhitespaceEtc(); + return result; + } - var result = new TomlTable {Defined = true}; + private TomlTable ReadInlineTable(TomletStringReader reader) + { + //Consume the opening brace + if (!reader.ExpectAndConsume('{')) + throw new ArgumentException("Internal Tomlet Bug: ReadInlineTable called and first char is not a {"); - while (reader.TryPeek(out _)) - { - //Skip any whitespace. Do not skip comments or newlines, those aren't allowed. - reader.SkipWhitespace(); + //Move to the first key + _lineNumber += reader.SkipAnyCommentNewlineWhitespaceEtc(); - if (!reader.TryPeek(out var nextChar)) - throw new TomlEndOfFileException(_lineNumber); + var result = new TomlTable {Defined = true}; - //Note that this is only needed when we first enter the loop, in case of an empty inline table - if (nextChar.IsEndOfInlineObjectChar()) - break; + while (reader.TryPeek(out _)) + { + //Skip any whitespace. Do not skip comments or newlines, those aren't allowed. + reader.SkipWhitespace(); - //Newlines are not permitted - if (nextChar.IsNewline()) - throw new NewLineInTomlInlineTableException(_lineNumber); + if (!reader.TryPeek(out var nextChar)) + throw new TomlEndOfFileException(_lineNumber); - //Note that unlike in the above case, we do not check for the end of the value here. Trailing commas aren't permitted - //and so all cases where the table ends should be handled at the end of this look - try - { - //Read a key-value pair - ReadKeyValuePair(reader, out var key, out var value); - //Insert into the table - result.ParserPutValue(key, value, _lineNumber); - } - catch (TomlException ex) when (ex is TomlMissingEqualsException or NoTomlKeyException or TomlWhitespaceInKeyException) - { - //Wrap missing keys or equals signs in a parent exception. - throw new InvalidTomlInlineTableException(_lineNumber, ex); - } + //Note that this is only needed when we first enter the loop, in case of an empty inline table + if (nextChar.IsEndOfInlineObjectChar()) + break; - if (!reader.TryPeek(out var postValueChar)) - throw new TomlEndOfFileException(_lineNumber); + //Newlines are not permitted + if (nextChar.IsNewline()) + throw new NewLineInTomlInlineTableException(_lineNumber); - if (reader.ExpectAndConsume(',')) - continue; //Comma, we have more. + //Note that unlike in the above case, we do not check for the end of the value here. Trailing commas aren't permitted + //and so all cases where the table ends should be handled at the end of this look + try + { + //Read a key-value pair + ReadKeyValuePair(reader, out var key, out var value); + //Insert into the table + result.ParserPutValue(key, value, _lineNumber); + } + catch (TomlException ex) when (ex is TomlMissingEqualsException or NoTomlKeyException or TomlWhitespaceInKeyException) + { + //Wrap missing keys or equals signs in a parent exception. + throw new InvalidTomlInlineTableException(_lineNumber, ex); + } - //Non-comma, consume any whitespace - reader.SkipWhitespace(); + if (!reader.TryPeek(out var postValueChar)) + throw new TomlEndOfFileException(_lineNumber); - if (!reader.TryPeek(out postValueChar)) - throw new TomlEndOfFileException(_lineNumber); + if (reader.ExpectAndConsume(',')) + continue; //Comma, we have more. - if (postValueChar.IsEndOfInlineObjectChar()) - break; //end of table + //Non-comma, consume any whitespace + reader.SkipWhitespace(); - throw new TomlInlineTableSeparatorException(_lineNumber, (char) postValueChar); - } + if (!reader.TryPeek(out postValueChar)) + throw new TomlEndOfFileException(_lineNumber); - reader.ExpectAndConsume('}'); + if (postValueChar.IsEndOfInlineObjectChar()) + break; //end of table - result.Locked = true; //Defined inline, cannot be later modified - return result; + throw new TomlInlineTableSeparatorException(_lineNumber, (char) postValueChar); } - private TomlTable ReadTableStatement(TomletStringReader reader, TomlDocument document) - { - //Table name - var currentTableKey = reader.ReadWhile(c => !c.IsEndOfArrayChar() && !c.IsNewline()); + reader.ExpectAndConsume('}'); - var parent = (TomlTable) document; - var relativeKey = currentTableKey; - FindParentAndRelativeKey(ref parent, ref relativeKey); + result.Locked = true; //Defined inline, cannot be later modified + return result; + } - TomlTable table; - try + private TomlTable ReadTableStatement(TomletStringReader reader, TomlDocument document) + { + //Table name + var currentTableKey = reader.ReadWhile(c => !c.IsEndOfArrayChar() && !c.IsNewline()); + + var parent = (TomlTable) document; + var relativeKey = currentTableKey; + FindParentAndRelativeKey(ref parent, ref relativeKey); + + TomlTable table; + try + { + if (parent.ContainsKey(relativeKey)) { - if (parent.ContainsKey(relativeKey)) + try { - try - { - table = (TomlTable) parent.GetValue(relativeKey); - - //The cast succeeded - we are defining an existing table - if (table.Defined) - { - // The table was not one created automatically - throw new TomlTableRedefinitionException(_lineNumber, currentTableKey); - } - } - catch (InvalidCastException) + table = (TomlTable) parent.GetValue(relativeKey); + + //The cast succeeded - we are defining an existing table + if (table.Defined) { - //The cast failed, we are re-defining a non-table. - throw new TomlKeyRedefinitionException(_lineNumber, currentTableKey); + // The table was not one created automatically + throw new TomlTableRedefinitionException(_lineNumber, currentTableKey); } } - else + catch (InvalidCastException) { - table = new TomlTable {Defined = true}; - parent.ParserPutValue(relativeKey, table, _lineNumber); + //The cast failed, we are re-defining a non-table. + throw new TomlKeyRedefinitionException(_lineNumber, currentTableKey); } } - catch (TomlContainsDottedKeyNonTableException e) + else { - //Re-throw with correct line number and exception type. - //To be clear - here we're re-defining a NON-TABLE key as a table, so this is a dotted key exception - //while the one above is a TableRedefinition exception because it's re-defining a key which is already a table. - throw new TomlDottedKeyParserException(_lineNumber, e.Key); + table = new TomlTable {Defined = true}; + parent.ParserPutValue(relativeKey, table, _lineNumber); } + } + catch (TomlContainsDottedKeyNonTableException e) + { + //Re-throw with correct line number and exception type. + //To be clear - here we're re-defining a NON-TABLE key as a table, so this is a dotted key exception + //while the one above is a TableRedefinition exception because it's re-defining a key which is already a table. + throw new TomlDottedKeyParserException(_lineNumber, e.Key); + } - if (!reader.TryPeek(out _)) - throw new TomlEndOfFileException(_lineNumber); + if (!reader.TryPeek(out _)) + throw new TomlEndOfFileException(_lineNumber); - if (!reader.ExpectAndConsume(']')) - throw new UnterminatedTomlTableNameException(_lineNumber); + if (!reader.ExpectAndConsume(']')) + throw new UnterminatedTomlTableNameException(_lineNumber); - reader.SkipWhitespace(); - table.Comments.InlineComment = ReadAnyPotentialInlineComment(reader); - reader.SkipPotentialCarriageReturn(); + reader.SkipWhitespace(); + table.Comments.InlineComment = ReadAnyPotentialInlineComment(reader); + reader.SkipPotentialCarriageReturn(); - if (!reader.TryPeek(out var shouldBeNewline)) - throw new TomlEndOfFileException(_lineNumber); + if (!reader.TryPeek(out var shouldBeNewline)) + throw new TomlEndOfFileException(_lineNumber); - if (!shouldBeNewline.IsNewline()) - throw new TomlMissingNewlineException(_lineNumber, (char) shouldBeNewline); + if (!shouldBeNewline.IsNewline()) + throw new TomlMissingNewlineException(_lineNumber, (char) shouldBeNewline); - _currentTable = table; + _currentTable = table; - //Save table names - _tableNames = currentTableKey.Split('.'); + //Save table names + _tableNames = currentTableKey.Split('.'); - return table; - } + return table; + } - private TomlArray ReadTableArrayStatement(TomletStringReader reader, TomlDocument document) - { - //Consume the (second) opening bracket - if (!reader.ExpectAndConsume('[')) - throw new ArgumentException("Internal Tomlet Bug: ReadTableArrayStatement called and first char is not a ["); + private TomlArray ReadTableArrayStatement(TomletStringReader reader, TomlDocument document) + { + //Consume the (second) opening bracket + if (!reader.ExpectAndConsume('[')) + throw new ArgumentException("Internal Tomlet Bug: ReadTableArrayStatement called and first char is not a ["); - //Array - var arrayName = reader.ReadWhile(c => !c.IsEndOfArrayChar() && !c.IsNewline()); + //Array + var arrayName = reader.ReadWhile(c => !c.IsEndOfArrayChar() && !c.IsNewline()); - if (!reader.ExpectAndConsume(']') || !reader.ExpectAndConsume(']')) - throw new UnterminatedTomlTableArrayException(_lineNumber); + if (!reader.ExpectAndConsume(']') || !reader.ExpectAndConsume(']')) + throw new UnterminatedTomlTableArrayException(_lineNumber); - TomlTable parentTable = document; - var relativeKey = arrayName; - FindParentAndRelativeKey(ref parentTable, ref relativeKey); + TomlTable parentTable = document; + var relativeKey = arrayName; + FindParentAndRelativeKey(ref parentTable, ref relativeKey); - if (parentTable == document) - { - if (relativeKey.Contains('.')) - throw new MissingIntermediateInTomlTableArraySpecException(_lineNumber, relativeKey); - } - - //Find existing array or make new one - TomlArray array; - if (parentTable.ContainsKey(relativeKey)) - { - var value = parentTable.GetValue(relativeKey); - if (value is TomlArray arr) - array = arr; - else - throw new TomlTableArrayAlreadyExistsAsNonArrayException(_lineNumber, arrayName); + if (parentTable == document) + { + if (relativeKey.Contains('.')) + throw new MissingIntermediateInTomlTableArraySpecException(_lineNumber, relativeKey); + } - if (!array.IsLockedToBeTableArray) - { - throw new TomlNonTableArrayUsedAsTableArrayException(_lineNumber, arrayName); - } - } + //Find existing array or make new one + TomlArray array; + if (parentTable.ContainsKey(relativeKey)) + { + var value = parentTable.GetValue(relativeKey); + if (value is TomlArray arr) + array = arr; else + throw new TomlTableArrayAlreadyExistsAsNonArrayException(_lineNumber, arrayName); + + if (!array.IsLockedToBeTableArray) { - array = new TomlArray {IsLockedToBeTableArray = true}; - //Insert into parent table - parentTable.ParserPutValue(relativeKey, array, _lineNumber); + throw new TomlNonTableArrayUsedAsTableArrayException(_lineNumber, arrayName); } + } + else + { + array = new TomlArray {IsLockedToBeTableArray = true}; + //Insert into parent table + parentTable.ParserPutValue(relativeKey, array, _lineNumber); + } - // Create new table and add it to the array - _currentTable = new TomlTable {Defined = true}; - array.ArrayValues.Add(_currentTable); + // Create new table and add it to the array + _currentTable = new TomlTable {Defined = true}; + array.ArrayValues.Add(_currentTable); - //Save table names - _tableNames = arrayName.Split('.'); + //Save table names + _tableNames = arrayName.Split('.'); - return array; - } + return array; + } - private void FindParentAndRelativeKey(ref TomlTable parent, ref string relativeName) + private void FindParentAndRelativeKey(ref TomlTable parent, ref string relativeName) + { + for (var index = 0; index < _tableNames.Length; index++) { - for (var index = 0; index < _tableNames.Length; index++) + var rootTableName = _tableNames[index]; + if (!relativeName.StartsWith(rootTableName + ".")) { - var rootTableName = _tableNames[index]; - if (!relativeName.StartsWith(rootTableName + ".")) - { - break; - } - - var value = parent.GetValue(rootTableName); - if (value is TomlTable subTable) - { - parent = subTable; - } - else if (value is TomlArray array) - { - parent = (TomlTable) array.Last(); - } - else - { - // Note: Expects either TomlArray or TomlTable - throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), typeof(TomlArray)); - } + break; + } - relativeName = relativeName.Substring(rootTableName.Length + 1); + var value = parent.GetValue(rootTableName); + if (value is TomlTable subTable) + { + parent = subTable; + } + else if (value is TomlArray array) + { + parent = (TomlTable) array.Last(); + } + else + { + // Note: Expects either TomlArray or TomlTable + throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), typeof(TomlArray)); } + + relativeName = relativeName.Substring(rootTableName.Length + 1); } + } - private string? ReadAnyPotentialInlineComment(TomletStringReader reader) - { - if (!reader.ExpectAndConsume('#')) - return null; //No comment + private string? ReadAnyPotentialInlineComment(TomletStringReader reader) + { + if (!reader.ExpectAndConsume('#')) + return null; //No comment - var ret = reader.ReadWhile(c => !c.IsNewline()).Trim(); + var ret = reader.ReadWhile(c => !c.IsNewline()).Trim(); - if (ret.Length < 1) - return null; + if (ret.Length < 1) + return null; - if(ret[0] == ' ') - ret = ret.Substring(1); + if(ret[0] == ' ') + ret = ret.Substring(1); - foreach (var i in ret.Select(c => (int) c)) - i.EnsureLegalChar(_lineNumber); + foreach (var i in ret.Select(c => (int) c)) + i.EnsureLegalChar(_lineNumber); - return ret; + return ret; - } + } - private string? ReadAnyPotentialMultilineComment(TomletStringReader reader) + private string? ReadAnyPotentialMultilineComment(TomletStringReader reader) + { + var ret = new StringBuilder(); + while (reader.ExpectAndConsume('#')) { - var ret = new StringBuilder(); - while (reader.ExpectAndConsume('#')) - { - var line = reader.ReadWhile(c => !c.IsNewline()); + var line = reader.ReadWhile(c => !c.IsNewline()); - if(line.Length > 0 && line[0] == ' ') - line = line.Substring(1); + if(line.Length > 0 && line[0] == ' ') + line = line.Substring(1); - foreach (var i in line.Select(c => (int) c)) - i.EnsureLegalChar(_lineNumber); + foreach (var i in line.Select(c => (int) c)) + i.EnsureLegalChar(_lineNumber); - ret.Append(line); + ret.Append(line); - _lineNumber += reader.SkipAnyNewlineOrWhitespace(); - } + _lineNumber += reader.SkipAnyNewlineOrWhitespace(); + } - if (ret.Length == 0) - return null; + if (ret.Length == 0) + return null; - return ret.ToString(); - } + return ret.ToString(); } } \ No newline at end of file diff --git a/Tomlet/TomlSerializationMethods.cs b/Tomlet/TomlSerializationMethods.cs index 7b58ad7..792d17b 100644 --- a/Tomlet/TomlSerializationMethods.cs +++ b/Tomlet/TomlSerializationMethods.cs @@ -1,413 +1,520 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; -using Tomlet.Attributes; using Tomlet.Exceptions; using Tomlet.Extensions; using Tomlet.Models; -namespace Tomlet +namespace Tomlet; + +public static class TomlSerializationMethods { - public static class TomlSerializationMethods - { - private static MethodInfo _stringKeyedDictionaryMethod = typeof(TomlSerializationMethods).GetMethod(nameof(StringKeyedDictionaryDeserializerFor), BindingFlags.Static | BindingFlags.NonPublic)!; - private static MethodInfo _primitiveKeyedDictionaryMethod = typeof(TomlSerializationMethods).GetMethod(nameof(PrimitiveKeyedDictionaryDeserializerFor), BindingFlags.Static | BindingFlags.NonPublic)!; - private static MethodInfo _genericDictionarySerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericDictionarySerializer), BindingFlags.Static | BindingFlags.NonPublic)!; - private static MethodInfo _genericNullableSerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericNullableSerializer), BindingFlags.Static | BindingFlags.NonPublic)!; +#if MODERN_DOTNET + internal const DynamicallyAccessedMemberTypes MainDeserializerAccessedMemberTypes = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.NonPublicProperties; +#endif + + private static MethodInfo _stringKeyedDictionaryMethod = typeof(TomlSerializationMethods).GetMethod(nameof(StringKeyedDictionaryDeserializerFor), BindingFlags.Static | BindingFlags.NonPublic)!; + private static MethodInfo _primitiveKeyedDictionaryMethod = typeof(TomlSerializationMethods).GetMethod(nameof(PrimitiveKeyedDictionaryDeserializerFor), BindingFlags.Static | BindingFlags.NonPublic)!; + private static MethodInfo _genericDictionarySerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericDictionarySerializer), BindingFlags.Static | BindingFlags.NonPublic)!; + private static MethodInfo _genericNullableSerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericNullableSerializer), BindingFlags.Static | BindingFlags.NonPublic)!; - public delegate T Deserialize(TomlValue value); - public delegate T ComplexDeserialize(TomlValue value, TomlSerializerOptions options); - public delegate TomlValue? Serialize(T? t); - public delegate TomlValue? ComplexSerialize(T? t, TomlSerializerOptions options); + public delegate T Deserialize(TomlValue value); + public delegate T ComplexDeserialize(TomlValue value, TomlSerializerOptions options); + public delegate TomlValue? Serialize(T? t); + public delegate TomlValue? ComplexSerialize(T? t, TomlSerializerOptions options); - private static readonly Dictionary Deserializers = new(); - private static readonly Dictionary Serializers = new(); + private static readonly Dictionary Deserializers = new(); + private static readonly Dictionary Serializers = new(); - [ExcludeFromCodeCoverage] - static TomlSerializationMethods() - { - //Register built-in serializers + [Attributes.ExcludeFromCodeCoverage] + static TomlSerializationMethods() + { + //Register built-in serializers - //String - Register(s => new TomlString(s!), value => (value as TomlString)?.Value ?? value.StringValue); + //String + Register(s => new TomlString(s!), value => (value as TomlString)?.Value ?? value.StringValue); - //Bool - Register(TomlBoolean.ValueOf, value => (value as TomlBoolean)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlBoolean), value.GetType(), typeof(bool))); + //Bool + Register(TomlBoolean.ValueOf, value => (value as TomlBoolean)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlBoolean), value.GetType(), typeof(bool))); - //Byte - Register(i => new TomlLong(i), value => (byte)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(byte)))); + //Byte + Register(i => new TomlLong(i), value => (byte)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(byte)))); - //SByte - Register(i => new TomlLong(i), value => (sbyte)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(sbyte)))); + //SByte + Register(i => new TomlLong(i), value => (sbyte)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(sbyte)))); - //UShort - Register(i => new TomlLong(i), value => (ushort)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(ushort)))); + //UShort + Register(i => new TomlLong(i), value => (ushort)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(ushort)))); - //Short - Register(i => new TomlLong(i), value => (short)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(short)))); + //Short + Register(i => new TomlLong(i), value => (short)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(short)))); - //UInt - Register(i => new TomlLong(i), value => (uint)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(uint)))); + //UInt + Register(i => new TomlLong(i), value => (uint)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(uint)))); - //Int - Register(i => new TomlLong(i), value => (int)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(int)))); + //Int + Register(i => new TomlLong(i), value => (int)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(int)))); - //ULong - Register(l => new TomlLong((long)l), value => (ulong)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(ulong)))); + //ULong + Register(l => new TomlLong((long)l), value => (ulong)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(ulong)))); - //Long - Register(l => new TomlLong(l), value => (value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(long))); + //Long + Register(l => new TomlLong(l), value => (value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(long))); - //Double - Register(d => new TomlDouble(d), value => (value as TomlDouble)?.Value ?? (value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlDouble), value.GetType(), typeof(double))); + //Double + Register(d => new TomlDouble(d), value => (value as TomlDouble)?.Value ?? (value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlDouble), value.GetType(), typeof(double))); - //Float - Register(f => new TomlDouble(f), value => (float)((value as TomlDouble)?.Value ?? (value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlDouble), value.GetType(), typeof(float)))); + //Float + Register(f => new TomlDouble(f), value => (float)((value as TomlDouble)?.Value ?? (value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlDouble), value.GetType(), typeof(float)))); - //LocalDate(Time) - Register(dt => dt.TimeOfDay == TimeSpan.Zero ? new TomlLocalDate(dt) : new TomlLocalDateTime(dt), value => (value as ITomlValueWithDateTime)?.Value ?? throw new TomlTypeMismatchException(typeof(ITomlValueWithDateTime), value.GetType(), typeof(DateTime))); + //LocalDate(Time) + Register(dt => dt.TimeOfDay == TimeSpan.Zero ? new TomlLocalDate(dt) : new TomlLocalDateTime(dt), value => (value as ITomlValueWithDateTime)?.Value ?? throw new TomlTypeMismatchException(typeof(ITomlValueWithDateTime), value.GetType(), typeof(DateTime))); - //OffsetDateTime - Register(odt => new TomlOffsetDateTime(odt), value => (value as TomlOffsetDateTime)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlOffsetDateTime), value.GetType(), typeof(DateTimeOffset))); + //OffsetDateTime + Register(odt => new TomlOffsetDateTime(odt), value => (value as TomlOffsetDateTime)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlOffsetDateTime), value.GetType(), typeof(DateTimeOffset))); - //LocalTime - Register(lt => new TomlLocalTime(lt), value => (value as TomlLocalTime)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLocalTime), value.GetType(), typeof(TimeSpan))); - } + //LocalTime + Register(lt => new TomlLocalTime(lt), value => (value as TomlLocalTime)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLocalTime), value.GetType(), typeof(TimeSpan))); + } - /// - /// Returns the default (reflection-based) serializer for the given type. Can be useful if you're implementing your own custom serializer but want to use the default behavior (e.g. to extend it or to use it as a fallback). - /// - /// The type to get the default serializer for - /// The options to use for the serializer - /// The default reflection-based serializer for the given type. - /// Thrown if is a primitive type. - public static Serialize GetDefaultSerializerForType(Type type, TomlSerializerOptions? options = null) - { - options ??= TomlSerializerOptions.Default; - if(type.IsPrimitive) - throw new ArgumentException("Can't use reflection-based serializer for primitive types.", nameof(type)); + /// + /// Returns the default (reflection-based) serializer for the given type. Can be useful if you're implementing your own custom serializer but want to use the default behavior (e.g. to extend it or to use it as a fallback). + /// + /// The type to get the default serializer for + /// The options to use for the serializer + /// The default reflection-based serializer for the given type. + /// Thrown if is a primitive type. +#if MODERN_DOTNET + public static Serialize GetDefaultSerializerForType([DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] Type type, TomlSerializerOptions? options = null) +#else + public static Serialize GetDefaultSerializerForType(Type type, TomlSerializerOptions? options = null) +#endif + { + options ??= TomlSerializerOptions.Default; + if(type.IsPrimitive) + throw new ArgumentException("Can't use reflection-based serializer for primitive types.", nameof(type)); - return TomlCompositeSerializer.For(type, options); - } + return TomlCompositeSerializer.For(type, options); + } - /// - /// Returns the default (reflection-based) deserializer for the given type. Can be useful if you're implementing your own custom deserializer but want to use the default behavior (e.g. to extend it or to use it as a fallback). - /// - /// The type to get the default deserializer for - /// The options to use for the deserializer - /// The default reflection-based deserializer for the given type. - /// Thrown if is a primitive type. - public static Deserialize GetDefaultDeserializerForType(Type type, TomlSerializerOptions? options = null) - { - options ??= TomlSerializerOptions.Default; - if(type.IsPrimitive) - throw new ArgumentException("Can't use reflection-based deserializer for primitive types.", nameof(type)); + /// + /// Returns the default (reflection-based) deserializer for the given type. Can be useful if you're implementing your own custom deserializer but want to use the default behavior (e.g. to extend it or to use it as a fallback). + /// + /// The type to get the default deserializer for + /// The options to use for the deserializer + /// The default reflection-based deserializer for the given type. + /// Thrown if is a primitive type. +#if MODERN_DOTNET + public static Deserialize GetDefaultDeserializerForType([DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] Type type, TomlSerializerOptions? options = null) +#else + public static Deserialize GetDefaultDeserializerForType(Type type, TomlSerializerOptions? options = null) +#endif + { + options ??= TomlSerializerOptions.Default; + if(type.IsPrimitive) + throw new ArgumentException("Can't use reflection-based deserializer for primitive types.", nameof(type)); - return TomlCompositeDeserializer.For(type, options); - } + return TomlCompositeDeserializer.For(type, options); + } +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + internal static Serialize GetSerializer([DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] Type t, TomlSerializerOptions? options) +#else internal static Serialize GetSerializer(Type t, TomlSerializerOptions? options) - { - options ??= TomlSerializerOptions.Default; +#endif + { + options ??= TomlSerializerOptions.Default; - if (Serializers.TryGetValue(t, out var value)) - return (Serialize)value; + if (Serializers.TryGetValue(t, out var value)) + return (Serialize)value; - //First check, lists and arrays get serialized as enumerables. - if (t.IsArray || t is { Namespace: "System.Collections.Generic", Name: "List`1" }) + //First check, lists and arrays get serialized as enumerables. + if (t.IsArray || t is { Namespace: "System.Collections.Generic", Name: "List`1" }) + { + var arrSerializer = GenericEnumerableSerializer(); + Serializers[t] = arrSerializer; + return arrSerializer; + } + + //Check for dicts and nullables + if (t.IsGenericType) + { + if (t.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { - var arrSerializer = GenericEnumerableSerializer(); - Serializers[t] = arrSerializer; - return arrSerializer; + return DictionarySerializerFor(t, options); } - //Check for dicts and nullables - if (t.IsGenericType && t.GetGenericArguments() is { } genericArgs) + if (t.GetGenericTypeDefinition() == typeof(Nullable<>)) { - if (t.GetGenericTypeDefinition() == typeof(Dictionary<,>)) - { - var serializer = _genericDictionarySerializerMethod.MakeGenericMethod(genericArgs); - - var del = Delegate.CreateDelegate(typeof(ComplexSerialize<>).MakeGenericType(t), serializer); - var ret = (Serialize)(dict => (TomlValue?)del.DynamicInvoke(dict, options)); - Serializers[t] = ret; + return NullableSerializerFor(t, options); + } + } + + //Now we've got dicts out of the way we can check if we're dealing with something that's IEnumerable and if so serialize as an array. We do this only *after* checking for dictionaries, because we don't want to serialize dictionaries as enumerables (i.e. table-arrays) + if (typeof(IEnumerable).IsAssignableFrom(t)) + { + var enumerableSerializer = GenericEnumerableSerializer(); + Serializers[t] = enumerableSerializer; + return enumerableSerializer; + } - return ret; - } + return TomlCompositeSerializer.For(t, options); + } - if (t.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - var serializer = _genericNullableSerializerMethod.MakeGenericMethod(genericArgs); - - var del = Delegate.CreateDelegate(typeof(ComplexSerialize<>).MakeGenericType(t), serializer); - var ret = (Serialize)(dict => (TomlValue?)del.DynamicInvoke(dict, options)); - Serializers[t] = ret; - - return ret; - } - } +#if MODERN_DOTNET + [UnconditionalSuppressMessage("AOT", "IL2060", Justification = "Any provided list, enumerable, nullable, or array type's underlying generic arguments must have been used somewhere in the consuming code in order for this method to be called, so the dynamic code requirement is already satisfied.")] + [UnconditionalSuppressMessage("AOT", "IL2062", Justification = "Any provided list, enumerable, nullable, or array type's underlying generic arguments must have been used somewhere in the consuming code in order for this method to be called, so the dynamic code requirement is already satisfied.")] + [UnconditionalSuppressMessage("AOT", "IL2072", Justification = "Any provided list, enumerable, nullable, or array type's underlying generic arguments must have been used somewhere in the consuming code in order for this method to be called, so the dynamic code requirement is already satisfied.")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of deserialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + internal static Deserialize GetDeserializer([DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] Type t, TomlSerializerOptions? options) +#else + internal static Deserialize GetDeserializer(Type t, TomlSerializerOptions? options) +#endif + { + options ??= TomlSerializerOptions.Default; - //Now we've got dicts out of the way we can check if we're dealing with something that's IEnumerable and if so serialize as an array. We do this only *after* checking for dictionaries, because we don't want to serialize dictionaries as enumerables (i.e. table-arrays) - if (typeof(IEnumerable).IsAssignableFrom(t)) - { - var enumerableSerializer = GenericEnumerableSerializer(); - Serializers[t] = enumerableSerializer; - return enumerableSerializer; - } + if (Deserializers.TryGetValue(t, out var value)) + return (Deserialize)value; - return TomlCompositeSerializer.For(t, options); + //We allow deserializing to IEnumerable fields/props, by setting them an array. We DO NOT do anything for classes that implement IEnumerable, though, because that would mess with deserializing lists, dictionaries, etc. + if (t.IsArray || t.IsInterface && typeof(IEnumerable).IsAssignableFrom(t)) + { + var elementType = t.IsInterface ? t.GetGenericArguments()[0] : t.GetElementType()!; + var arrayDeserializer = ArrayDeserializerFor(elementType, options); + Deserializers[t] = arrayDeserializer; + return arrayDeserializer; } - internal static Deserialize GetDeserializer(Type t, TomlSerializerOptions? options) + if (t.Namespace == "System.Collections.Generic" && t.Name == "List`1") { - options ??= TomlSerializerOptions.Default; + var listDeserializer = ListDeserializerFor(t.GetGenericArguments()[0], options); + Deserializers[t] = listDeserializer; + return listDeserializer; + } - if (Deserializers.TryGetValue(t, out var value)) - return (Deserialize)value; + if(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>) && t.GetGenericArguments() is {Length: 1}) + { + var nullableDeserializer = NullableDeserializerFor(t, options); + Deserializers[t] = nullableDeserializer; + return nullableDeserializer; + } - //We allow deserializing to IEnumerable fields/props, by setting them an array. We DO NOT do anything for classes that implement IEnumerable, though, because that would mess with deserializing lists, dictionaries, etc. - if (t.IsArray || t.IsInterface && typeof(IEnumerable).IsAssignableFrom(t)) + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>) && t.GetGenericArguments() is { Length: 2 } genericArgs) + { + if (genericArgs[0] == typeof(string)) { - var elementType = t.IsInterface ? t.GetGenericArguments()[0] : t.GetElementType()!; - var arrayDeserializer = ArrayDeserializerFor(elementType, options); - Deserializers[t] = arrayDeserializer; - return arrayDeserializer; + return (Deserialize)_stringKeyedDictionaryMethod.MakeGenericMethod(genericArgs[1]).Invoke(null, new object[]{options})!; } - if (t.Namespace == "System.Collections.Generic" && t.Name == "List`1") - { - var listDeserializer = ListDeserializerFor(t.GetGenericArguments()[0], options); - Deserializers[t] = listDeserializer; - return listDeserializer; - } - - if(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>) && t.GetGenericArguments() is {Length: 1}) + if (genericArgs[0].IsIntegerType() || genericArgs[0] == typeof(bool) || genericArgs[0] == typeof(char)) { - var nullableDeserializer = NullableDeserializerFor(t, options); - Deserializers[t] = nullableDeserializer; - return nullableDeserializer; + // float primitives not supported due to decimal point causing issues + return (Deserialize)_primitiveKeyedDictionaryMethod.MakeGenericMethod(genericArgs).Invoke(null, new object[]{options})!; } + } - if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>) && t.GetGenericArguments() is { Length: 2 } genericArgs) - { - if (genericArgs[0] == typeof(string)) - { - return (Deserialize)_stringKeyedDictionaryMethod.MakeGenericMethod(genericArgs[1]).Invoke(null, new object[]{options})!; - } - - if (genericArgs[0].IsIntegerType() || genericArgs[0] == typeof(bool) || genericArgs[0] == typeof(char)) - { - // float primitives not supported due to decimal point causing issues - return (Deserialize)_primitiveKeyedDictionaryMethod.MakeGenericMethod(genericArgs).Invoke(null, new object[]{options})!; - } - } + return TomlCompositeDeserializer.For(t, options); + } - return TomlCompositeDeserializer.For(t, options); - } + private static Serialize GenericEnumerableSerializer() => + o => + { + if (o is not IEnumerable arr) + throw new Exception("How did ArraySerializer end up getting a non-array?"); - private static Serialize GenericEnumerableSerializer() => - o => + var ret = new TomlArray(); + foreach (var entry in arr) { - if (o is not IEnumerable arr) - throw new Exception("How did ArraySerializer end up getting a non-array?"); + ret.Add(entry); + } - var ret = new TomlArray(); - foreach (var entry in arr) - { - ret.Add(entry); - } + return ret; + }; + +#if MODERN_DOTNET + [UnconditionalSuppressMessage("AOT", "IL2060", Justification = "The dictType's underlying generic arguments must have been used somewhere in the consuming code in order for this method to be called, so the dynamic code requirement is already satisfied.")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + private static Serialize DictionarySerializerFor([DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] Type dictType, TomlSerializerOptions options) +#else + private static Serialize DictionarySerializerFor(Type dictType, TomlSerializerOptions options) +#endif + { + var serializer = _genericDictionarySerializerMethod.MakeGenericMethod(dictType.GetGenericArguments()); - return ret; - }; + var del = Delegate.CreateDelegate(typeof(ComplexSerialize<>).MakeGenericType(dictType), serializer); + var ret = (Serialize)(dict => (TomlValue?)del.DynamicInvoke(dict, options)); + Serializers[dictType] = ret; + return ret; + } + +#if MODERN_DOTNET + [UnconditionalSuppressMessage("AOT", "IL2060", Justification = "The nullableType's underlying T must have been used somewhere in the consuming code in order for this method to be called, so the dynamic code requirement is already satisfied.")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + private static Serialize NullableSerializerFor([DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] Type nullableType, TomlSerializerOptions options) +#else + private static Serialize NullableSerializerFor(Type nullableType, TomlSerializerOptions options) +#endif + { + var serializer = _genericNullableSerializerMethod.MakeGenericMethod(nullableType.GetGenericArguments()); + + var del = Delegate.CreateDelegate(typeof(ComplexSerialize<>).MakeGenericType(nullableType), serializer); + var ret = (Serialize)(dict => (TomlValue?)del.DynamicInvoke(dict, options)); + Serializers[nullableType] = ret; + + return ret; + } + +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of deserialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + private static Deserialize ArrayDeserializerFor([DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] Type elementType, TomlSerializerOptions options) => +#else private static Deserialize ArrayDeserializerFor(Type elementType, TomlSerializerOptions options) => - value => - { - if (value is not TomlArray tomlArray) - throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), elementType.MakeArrayType()); +#endif + value => + { + if (value is not TomlArray tomlArray) + throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), elementType.MakeArrayType()); - var ret = Array.CreateInstance(elementType, tomlArray.Count); - var deserializer = GetDeserializer(elementType, options); - for (var index = 0; index < tomlArray.ArrayValues.Count; index++) - { - var arrayValue = tomlArray.ArrayValues[index]; - ret.SetValue(deserializer(arrayValue), index); - } + var ret = Array.CreateInstance(elementType, tomlArray.Count); + var deserializer = GetDeserializer(elementType, options); + for (var index = 0; index < tomlArray.ArrayValues.Count; index++) + { + var arrayValue = tomlArray.ArrayValues[index]; + ret.SetValue(deserializer(arrayValue), index); + } - return ret; - }; + return ret; + }; +#if MODERN_DOTNET + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "The element type must have been used somewhere in the consuming code in order for this method to be called, so the dynamic code requirement is already satisfied.")] + private static Deserialize ListDeserializerFor([DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] Type elementType, TomlSerializerOptions options) +#else private static Deserialize ListDeserializerFor(Type elementType, TomlSerializerOptions options) - { - var listType = typeof(List<>).MakeGenericType(elementType); - var relevantAddMethod = listType.GetMethod("Add")!; +#endif + { + var listType = typeof(List<>).MakeGenericType(elementType); + var relevantAddMethod = listType.GetMethod("Add")!; - return value => - { - if (value is not TomlArray tomlArray) - throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), listType); + return value => + { + if (value is not TomlArray tomlArray) + throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), listType); - var ret = Activator.CreateInstance(listType)!; - var deserializer = GetDeserializer(elementType, options); + var ret = Activator.CreateInstance(listType)!; + var deserializer = GetDeserializer(elementType, options); - foreach (var arrayValue in tomlArray.ArrayValues) - { - relevantAddMethod.Invoke(ret, new[] { deserializer(arrayValue) }); - } + foreach (var arrayValue in tomlArray.ArrayValues) + { + relevantAddMethod.Invoke(ret, new[] { deserializer(arrayValue) }); + } - return ret; - }; - } + return ret; + }; + } +#if MODERN_DOTNET + [UnconditionalSuppressMessage("AOT", "IL2062", Justification = "The nullableType's underlying T must have been used somewhere in the consuming code in order for this method to be called, so the dynamic code requirement is already satisfied.")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of deserialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + private static Deserialize NullableDeserializerFor([DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] Type nullableType, TomlSerializerOptions options) +#else private static Deserialize NullableDeserializerFor(Type nullableType, TomlSerializerOptions options) - { - var elementType = nullableType.GetGenericArguments()[0]; - var elementDeserializer = GetDeserializer(elementType, options); +#endif + { + var elementType = nullableType.GetGenericArguments()[0]; + var elementDeserializer = GetDeserializer(elementType, options); - return value => - { - //If we're deserializing, we know the value is not null - var element = elementDeserializer(value); - return Activator.CreateInstance(nullableType, element)!; - }; - } + return value => + { + //If we're deserializing, we know the value is not null + var element = elementDeserializer(value); + return Activator.CreateInstance(nullableType, element)!; + }; + } +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of deserialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + private static Deserialize> StringKeyedDictionaryDeserializerFor<[DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] T>(TomlSerializerOptions options) +#else private static Deserialize> StringKeyedDictionaryDeserializerFor(TomlSerializerOptions options) - { - var deserializer = GetDeserializer(typeof(T), options); +#endif + { + var deserializer = GetDeserializer(typeof(T), options); - return value => - { - if (value is not TomlTable table) - throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary)); + return value => + { + if (value is not TomlTable table) + throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary)); - return table.Entries.ToDictionary(entry => entry.Key, entry => (T)deserializer(entry.Value)); - }; - } + return table.Entries.ToDictionary(entry => entry.Key, entry => (T)deserializer(entry.Value)); + }; + } - // unmanaged + IConvertible is the closest I can get to expressing "primitives only" + // unmanaged + IConvertible is the closest I can get to expressing "primitives only" +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of deserialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + private static Deserialize> PrimitiveKeyedDictionaryDeserializerFor(TomlSerializerOptions options) where TKey : unmanaged, IConvertible +#else private static Deserialize> PrimitiveKeyedDictionaryDeserializerFor(TomlSerializerOptions options) where TKey : unmanaged, IConvertible - { - var valueDeserializer = GetDeserializer(typeof(TValue), options); +#endif + { + var valueDeserializer = GetDeserializer(typeof(TValue), options); - return value => - { - if (value is not TomlTable table) - throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary)); - - return table.Entries.ToDictionary( - entry => (TKey)(entry.Key as IConvertible).ToType(typeof(TKey), CultureInfo.InvariantCulture), - entry => (TValue)valueDeserializer(entry.Value) - ); - }; - } + return value => + { + if (value is not TomlTable table) + throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary)); + + return table.Entries.ToDictionary( + entry => (TKey)(entry.Key as IConvertible).ToType(typeof(TKey), CultureInfo.InvariantCulture), + entry => (TValue)valueDeserializer(entry.Value) + ); + }; + } +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + private static TomlValue? GenericNullableSerializer<[DynamicallyAccessedMembers(MainDeserializerAccessedMemberTypes)] T>(T? nullable, TomlSerializerOptions options) where T : struct +#else private static TomlValue? GenericNullableSerializer(T? nullable, TomlSerializerOptions options) where T : struct - { - var elementSerializer = GetSerializer(typeof(T), options); +#endif + { + var elementSerializer = GetSerializer(typeof(T), options); - if (nullable.HasValue) - return elementSerializer(nullable.Value); + if (nullable.HasValue) + return elementSerializer(nullable.Value); - return null; - } + return null; + } +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + private static TomlValue GenericDictionarySerializer(Dictionary dict, TomlSerializerOptions options) where TKey : notnull +#else private static TomlValue GenericDictionarySerializer(Dictionary dict, TomlSerializerOptions options) where TKey : notnull - { - var valueSerializer = GetSerializer(typeof(TValue), options); +#endif + { + var valueSerializer = GetSerializer(typeof(TValue), options); - var ret = new TomlTable(); - foreach (var entry in dict) - { - var keyAsString = entry.Key.ToString(); + var ret = new TomlTable(); + foreach (var entry in dict) + { + var keyAsString = entry.Key.ToString(); - if(keyAsString == null) - continue; + if(keyAsString == null) + continue; - var value = valueSerializer(entry.Value); + var value = valueSerializer(entry.Value); - if(value == null) - continue; + if(value == null) + continue; - ret.PutValue(keyAsString, value, true); - } - - return ret; + ret.PutValue(keyAsString, value, true); } - internal static void Register(Serialize? serializer, Deserialize? deserializer) - { - if (serializer != null) - { - RegisterSerializer(serializer); - - RegisterDictionarySerializer(serializer); - } - - if (deserializer != null) - { - RegisterDeserializer(deserializer); - RegisterDictionaryDeserializer(deserializer); - } - } + return ret; + } - internal static void Register(Type t, Serialize? serializer, Deserialize? deserializer) + internal static void Register(Serialize? serializer, Deserialize? deserializer) + { + if (serializer != null) { - if (serializer != null) - RegisterSerializer(serializer); + RegisterSerializer(serializer); - if (deserializer != null) - RegisterDeserializer(deserializer); + RegisterDictionarySerializer(serializer); } - private static void RegisterDeserializer(Deserialize deserializer) + if (deserializer != null) { - object BoxedDeserializer(TomlValue value) => deserializer.Invoke(value) ?? throw new Exception($"TOML Deserializer returned null for type {nameof(T)}"); - Deserializers[typeof(T)] = (Deserialize)BoxedDeserializer; + RegisterDeserializer(deserializer); + RegisterDictionaryDeserializer(deserializer); } + } - private static void RegisterSerializer(Serialize serializer) - { - TomlValue? ObjectAcceptingSerializer(object value) => serializer.Invoke((T)value); - Serializers[typeof(T)] = (Serialize)ObjectAcceptingSerializer!; - } + internal static void Register(Type t, Serialize? serializer, Deserialize? deserializer) + { + if (serializer != null) + RegisterSerializer(serializer); - private static void RegisterDictionarySerializer(Serialize serializer) - { - RegisterSerializer>(dict => - { - var table = new TomlTable(); + if (deserializer != null) + RegisterDeserializer(deserializer); + } - if (dict == null) - return table; + private static void RegisterDeserializer(Deserialize deserializer) + { + object BoxedDeserializer(TomlValue value) => deserializer.Invoke(value) ?? throw new Exception($"TOML Deserializer returned null for type {nameof(T)}"); + Deserializers[typeof(T)] = (Deserialize)BoxedDeserializer; + } - var keys = dict.Keys.ToList(); - var values = dict.Values.Select(serializer.Invoke).ToList(); + private static void RegisterSerializer(Serialize serializer) + { + TomlValue? ObjectAcceptingSerializer(object value) => serializer.Invoke((T)value); + Serializers[typeof(T)] = (Serialize)ObjectAcceptingSerializer!; + } - for (var i = 0; i < keys.Count; i++) - { - table.PutValue(keys[i], values[i], true); - } + private static void RegisterDictionarySerializer(Serialize serializer) + { + RegisterSerializer>(dict => + { + var table = new TomlTable(); + if (dict == null) return table; - }); - } - private static void RegisterDictionaryDeserializer(Deserialize deserializer) - { - RegisterDeserializer(value => + var keys = dict.Keys.ToList(); + var values = dict.Values.Select(serializer.Invoke).ToList(); + + for (var i = 0; i < keys.Count; i++) { - if (value is not TomlTable table) - throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary)); + var tomlValue = values[i]; + if (tomlValue == null) + //Skip null values + continue; + + table.PutValue(keys[i], tomlValue, true); + } - return table.Entries - .Select(kvp => new KeyValuePair(kvp.Key, deserializer.Invoke(kvp.Value))) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - }); - } + return table; + }); + } + + private static void RegisterDictionaryDeserializer(Deserialize deserializer) + { + RegisterDeserializer(value => + { + if (value is not TomlTable table) + throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary)); + + return table.Entries + .Select(kvp => new KeyValuePair(kvp.Key, deserializer.Invoke(kvp.Value))) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + }); } } \ No newline at end of file diff --git a/Tomlet/TomlSerializerOptions.cs b/Tomlet/TomlSerializerOptions.cs index 8a8c0f2..54b79be 100644 --- a/Tomlet/TomlSerializerOptions.cs +++ b/Tomlet/TomlSerializerOptions.cs @@ -1,9 +1,8 @@ -namespace Tomlet +namespace Tomlet; + +public class TomlSerializerOptions { - public class TomlSerializerOptions - { - public static TomlSerializerOptions Default = new(); - public bool OverrideConstructorValues { get; set; } = false; - public bool IgnoreNonPublicMembers { get; set; } = false; - } + public static TomlSerializerOptions Default = new(); + public bool OverrideConstructorValues { get; set; } = false; + public bool IgnoreNonPublicMembers { get; set; } = false; } \ No newline at end of file diff --git a/Tomlet/TomlUtils.cs b/Tomlet/TomlUtils.cs index 26c5f28..785bd9a 100644 --- a/Tomlet/TomlUtils.cs +++ b/Tomlet/TomlUtils.cs @@ -1,27 +1,26 @@ using Tomlet.Exceptions; -namespace Tomlet +namespace Tomlet; + +internal static class TomlUtils { - internal static class TomlUtils + public static string EscapeStringValue(string key) { - public static string EscapeStringValue(string key) - { - var escaped = key.Replace(@"\", @"\\") - .Replace("\n", @"\n") - .Replace("\r", ""); + var escaped = key.Replace(@"\", @"\\") + .Replace("\n", @"\n") + .Replace("\r", ""); - return escaped; - } + return escaped; + } - public static string AddCorrectQuotes(string key) - { - if (key.Contains("'") && key.Contains("\"")) - throw new InvalidTomlKeyException(key); + public static string AddCorrectQuotes(string key) + { + if (key.Contains("'") && key.Contains("\"")) + throw new InvalidTomlKeyException(key); - if (key.Contains("\"")) - return $"'{key}'"; + if (key.Contains("\"")) + return $"'{key}'"; - return $"\"{key}\""; - } + return $"\"{key}\""; } } \ No newline at end of file diff --git a/Tomlet/Tomlet.csproj b/Tomlet/Tomlet.csproj index 2cbd7d2..4393b38 100644 --- a/Tomlet/Tomlet.csproj +++ b/Tomlet/Tomlet.csproj @@ -21,11 +21,15 @@ true git https://github.com/SamboyCoding/Tomlet.git - net6;net7;netstandard2.0;netframework3.5 + net6;net7;net8;net9;netstandard2.0;netframework3.5 Tomlet 5.4.0 + true + SYSLIB0050 true + + MODERN_DOTNET diff --git a/Tomlet/TomletMain.cs b/Tomlet/TomletMain.cs index 3f9b31b..85b63bb 100644 --- a/Tomlet/TomletMain.cs +++ b/Tomlet/TomletMain.cs @@ -3,87 +3,161 @@ using Tomlet.Exceptions; using Tomlet.Models; -namespace Tomlet +namespace Tomlet; + +//Api class, these are supposed to be exposed +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] +public static class TomletMain { - //Api class, these are supposed to be exposed - [SuppressMessage("ReSharper", "UnusedMember.Global")] - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - public static class TomletMain + [Attributes.ExcludeFromCodeCoverage] + public static void RegisterMapper(TomlSerializationMethods.Serialize? serializer, TomlSerializationMethods.Deserialize? deserializer) + => TomlSerializationMethods.Register(serializer, deserializer); + +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of deserialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static T To<[DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] T>(string tomlString, TomlSerializerOptions? options = null) +#else + public static T To(string tomlString, TomlSerializerOptions? options = null) +#endif { - [Attributes.ExcludeFromCodeCoverage] - public static void RegisterMapper(TomlSerializationMethods.Serialize? serializer, TomlSerializationMethods.Deserialize? deserializer) - => TomlSerializationMethods.Register(serializer, deserializer); + return (T)To(typeof(T), tomlString, options); + } - public static T To(string tomlString, TomlSerializerOptions? options = null) - { - return (T)To(typeof(T), tomlString, options); - } - +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of deserialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static object To([DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] Type what, string tomlString, TomlSerializerOptions? options = null) +#else public static object To(Type what, string tomlString, TomlSerializerOptions? options = null) - { - var parser = new TomlParser(); - var tomlDocument = parser.Parse(tomlString); +#endif + { + var parser = new TomlParser(); + var tomlDocument = parser.Parse(tomlString); - return To(what, tomlDocument, options); - } + return To(what, tomlDocument, options); + } +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of deserialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static T To<[DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] T>(TomlValue value, TomlSerializerOptions? options = null) +#else public static T To(TomlValue value, TomlSerializerOptions? options = null) - { - return (T)To(typeof(T), value, options); - } +#endif + { + return (T)To(typeof(T), value, options); + } +#if MODERN_DOTNET +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of deserialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static object To([DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] Type what, TomlValue value, TomlSerializerOptions? options = null) +#else public static object To(Type what, TomlValue value, TomlSerializerOptions? options = null) - { - var deserializer = TomlSerializationMethods.GetDeserializer(what, options); - - return deserializer.Invoke(value); - } - -#if NET6_0 - [return: NotNullIfNotNull("t")] #endif - public static TomlValue? ValueFrom(T t, TomlSerializerOptions? options = null) - { - if (t == null) - throw new ArgumentNullException(nameof(t)); + { + var deserializer = TomlSerializationMethods.GetDeserializer(what, options); - return ValueFrom(t.GetType(), t, options); - } + return deserializer.Invoke(value); + } -#if NET6_0 - [return: NotNullIfNotNull("t")] +#if MODERN_DOTNET + [return: NotNullIfNotNull("t")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static TomlValue? ValueFrom<[DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] T>(T? t, TomlSerializerOptions? options = null) +#else + public static TomlValue? ValueFrom(T? t, TomlSerializerOptions? options = null) #endif - public static TomlValue? ValueFrom(Type type, object t, TomlSerializerOptions? options = null) - { - var serializer = TomlSerializationMethods.GetSerializer(type, options); + { + if (t == null) + return null; - var tomlValue = serializer.Invoke(t); + return ValueFrom(typeof(T), t, options); + } - return tomlValue!; - } +#if MODERN_DOTNET + [return: NotNullIfNotNull("t")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static TomlValue? ValueFrom([DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] Type type, object? t, TomlSerializerOptions? options = null) +#else + public static TomlValue? ValueFrom(Type type, object? t, TomlSerializerOptions? options = null) +#endif + { + if (t == null) + return null; + + var serializer = TomlSerializationMethods.GetSerializer(type, options); - public static TomlDocument DocumentFrom(T t, TomlSerializerOptions? options = null) - { - if (t == null) - throw new ArgumentNullException(nameof(t)); + var tomlValue = serializer.Invoke(t); - return DocumentFrom(t.GetType(), t, options); - } + return tomlValue!; + } - public static TomlDocument DocumentFrom(Type type, object t, TomlSerializerOptions? options = null) - { - var val = ValueFrom(type, t, options); +#if MODERN_DOTNET + [return: NotNullIfNotNull("t")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static TomlDocument? DocumentFrom<[DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] T>(T? t, TomlSerializerOptions? options = null) +#else + public static TomlDocument? DocumentFrom(T? t, TomlSerializerOptions? options = null) +#endif + { + if (t == null) + return null; - return val switch - { - TomlDocument doc => doc, - TomlTable table => new TomlDocument(table), - _ => throw new TomlPrimitiveToDocumentException(type) - }; - } + return DocumentFrom(typeof(T), t, options); + } - public static string TomlStringFrom(T t, TomlSerializerOptions? options = null) => DocumentFrom(t, options).SerializedValue; +#if MODERN_DOTNET + [return: NotNullIfNotNull("t")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static TomlDocument? DocumentFrom([DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] Type type, object? t, TomlSerializerOptions? options = null) +#else + public static TomlDocument? DocumentFrom(Type type, object? t, TomlSerializerOptions? options = null) +#endif + { + if (t == null) + return null; + + var val = ValueFrom(type, t, options); - public static string TomlStringFrom(Type type, object t, TomlSerializerOptions? options = null) => DocumentFrom(type, t, options).SerializedValue; + return val switch + { + TomlDocument doc => doc, + TomlTable table => new TomlDocument(table), + _ => throw new TomlPrimitiveToDocumentException(type) + }; } + +#if MODERN_DOTNET + [return: NotNullIfNotNull("t")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static string? TomlStringFrom<[DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] T>(T? t, TomlSerializerOptions? options = null) => DocumentFrom(t, options)?.SerializedValue; + + [return: NotNullIfNotNull("t")] +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for underlying implementations of serialize helper methods may not be available for a given type.")] +#endif // NET7_0_OR_GREATER + public static string? TomlStringFrom([DynamicallyAccessedMembers(TomlSerializationMethods.MainDeserializerAccessedMemberTypes)] Type type, object? t, TomlSerializerOptions? options = null) => DocumentFrom(type, t, options)?.SerializedValue; + +#else + public static string? TomlStringFrom(T? t, TomlSerializerOptions? options = null) => DocumentFrom(t, options)?.SerializedValue; + + public static string? TomlStringFrom(Type type, object? t, TomlSerializerOptions? options = null) => DocumentFrom(type, t, options)?.SerializedValue; +#endif } \ No newline at end of file