Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
59 changes: 35 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ For more information about TOML, visit the official website at [https://toml.io/

CsToml has the following features.

- It complies with [TOML v1.0.0](https://toml.io/en/v1.0.0).
- It complies with [TOML v1.0.0](https://toml.io/en/v1.0.0) by default.
- [TOML v1.1.0](https://toml.io/en/v1.1.0) is also supported as an optional feature.
- .NET 8, .NET 9, .NET 10 are supported.
- Parsing is performed using byte sequences instead of `string`.
- Byte sequences are processed directly by the API defined in `System.Buffers`(`IBufferWriter<byte>`,`ReadOnlySequence<byte>`), resulting in small memory allocation and fast performance.
- Buffers are rented from the pool(`ArrayPool<T>`), reducing the allocation.
- Core APIs are compatible with Native AOT.
- It supports new features planned for the upcoming TOML v1.1.0 as optional support.
- CsToml deserializer has been tested using [the standard TOML v1.0.0 test cases](https://github.com/toml-lang/toml-test/tree/master/tests) and all have passed.
- Deserialization has been tested using [the standard TOML v1.0.0 test cases and v1.1.0 test cases](https://github.com/toml-lang/toml-test/tree/master/tests) and all have passed.
- The serialization interface and implementation are influenced by [MemoryPack](https://github.com/Cysharp/MemoryPack) and [VYaml](https://github.com/hadashiA/VYaml).

Table of Contents
Expand All @@ -43,7 +43,8 @@ Table of Contents
* [Serialize API](#serialize-api)
* [Other Deserialize/Serialize APIs](#other-deserializeserialize-apis)
* [TomlDocument class](#tomldocument-class)
* [Pre-release version features overview](#pre-release-version-features-overview)
* [TOML v1.1.0 features overview](#toml-v110-features-overview)
* [Unofficial extension features overview](#unofficial-extension-features-overview)
* [Extensions (CsToml.Extensions)](#extensions-cstomlextensions)
* [Microsoft.Extensions.Configuration extensions (CsToml.Extensions.Configuration)](#microsoftextensionsconfiguration-extensions-cstomlextensionsconfiguration)
* [UnitTest](#unittest)
Expand Down Expand Up @@ -1518,42 +1519,32 @@ var document = CsTomlSerializer.Deserialize<TomlDocument>(tomlText);
var dict = document.ToDictionary<object, object>();
```

Pre-release version features overview
TOML v1.1.0 features overview
---

You can use the upcoming features planned for TOML v1.1.0, which has not been officially released yet.
Each feature can be enabled individually from CsTomlSerializerOptions.Spec.

> [!WARNING]
> As these are features from an unreleased version, they may be subject to specification changes or deprecation in the future.
Each feature in TOML v1.1.0 can be enabled individually or by using `TomlSpec.Version110` from `CsTomlSerializerOptions.Spec`.

```csharp
// using TomlSpec.Version110
var v110Options = CsTomlSerializerOptions.Default with
{
Spec = TomlSpec.Version110
};

// Or enable features individually
var v110Options = CsTomlSerializerOptions.Default with
{
Spec = new ()
{
AllowUnicodeInBareKeys = true,
AllowNewlinesInInlineTables = true,
AllowTrailingCommaInInlineTables = true,
AllowSecondsOmissionInTime = true,
SupportsEscapeSequenceE = true,
SupportsEscapeSequenceX = true,
}
};
var document = CsTomlSerializer.Deserialize<TomlDocument>(tomlText, v110Options);
```

### AllowUnicodeInBareKeys

This feature enables the use of Unicode characters (non-ASCII) in unquoted (bare) keys. This allows you to use characters from non-English scripts directly in key names without requiring quotation marks.
For example:

```toml
€ = 'Euro'
😂 = ""rofl""
a‍b = ""zwj""
ÅÅ = ""U+00C5 U+0041 U+030A""
あイ宇絵ォ = ""Japanese""
var document = CsTomlSerializer.Deserialize<TomlDocument>(tomlText, v110Options);
```

### AllowNewlinesInInlineTables
Expand Down Expand Up @@ -1606,6 +1597,25 @@ For example:
name = "this is \x43\x73\x54\x6f\x6d\x6c"
```

Unofficial extension features overview
---

This is an *unofficial extension* to the TOML specification and may not be supported by all TOML parsers.
Each feature can be enabled individually via `CsTomlSerializerOptions.Spec`.

### AllowUnicodeInBareKeys

This feature enables the use of Unicode characters (non-ASCII) in unquoted (bare) keys. This allows you to use characters from non-English scripts directly in key names without requiring quotation marks.
For example:

```toml
€ = 'Euro'
😂 = ""rofl""
a‍b = ""zwj""
ÅÅ = ""U+00C5 U+0041 U+030A""
あイ宇絵ォ = ""Japanese""
```

Extensions (`CsToml.Extensions`)
---

Expand Down Expand Up @@ -1666,6 +1676,7 @@ UnitTest
---

Please note that we are using the TOML files located in the ['tests/' directory of the ‘toml-test repository (MIT License)’](https://github.com/toml-lang/toml-test/tree/master/tests) for some of our unit tests.
The location is [here](https://github.com/prozolic/CsToml/tree/main/tests/CsToml.Tests/toml-test).

License
---
Expand Down
43 changes: 42 additions & 1 deletion src/CsToml/CsTomlReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ private void ReadKeyToNotAllowUnicodeInBareKeys(ref ExtendableArray<TomlDottedKe
dot = false;
key.Add(ReadSingleQuoteSingleLineString<TomlLiteralDottedKey>());
continue;
case TomlCodes.Symbol.NUMBERSIGN:
SkipOneLine();
SkipWhiteSpace();
continue;
default:
ExceptionHelper.ThrowKeyContainsInvalid(c);
break;
Expand Down Expand Up @@ -244,6 +248,7 @@ public void ReadTableHeaderToAllowUnicodeInBareKeys(ref ExtendableArray<TomlDott
tableHeaderKey.Add(ReadSingleQuoteSingleLineString<TomlLiteralDottedKey>());
continue;
case TomlCodes.Symbol.RIGHTSQUAREBRACKET:
if (dot) ExceptionHelper.ThrowKeyisNotSpecifiedAfterDot();
closingRightRightSquareBracket = true;
Advance(1);
goto BREAK; // ]
Expand Down Expand Up @@ -304,6 +309,7 @@ public void ReadTableHeaderToNotAllowUnicodeInBareKeys(ref ExtendableArray<TomlD
tableHeaderKey.Add(ReadSingleQuoteSingleLineString<TomlLiteralDottedKey>());
continue;
case TomlCodes.Symbol.RIGHTSQUAREBRACKET:
if (dot) ExceptionHelper.ThrowKeyisNotSpecifiedAfterDot();
closingRightRightSquareBracket = true;
Advance(1);
goto BREAK; // ]
Expand Down Expand Up @@ -1410,6 +1416,11 @@ private TomlInlineTable ReadInlineTable()
if (spec.AllowNewlinesInInlineTables) // TOML v1.1.0
{
SkipWhiteSpaceAndNewLine();
while (TryPeek(out var commentCh) && commentCh == TomlCodes.Symbol.NUMBERSIGN)
{
SkipOneLine();
SkipWhiteSpaceAndNewLine();
}
}
else
{
Expand All @@ -1433,6 +1444,11 @@ private TomlInlineTable ReadInlineTable()
if (spec.AllowNewlinesInInlineTables) // TOML v1.1.0
{
SkipWhiteSpaceAndNewLine();
while (TryPeek(out var commentCh) && commentCh == TomlCodes.Symbol.NUMBERSIGN)
{
SkipOneLine();
SkipWhiteSpaceAndNewLine();
}
if (TryPeek(out var ch2) && TomlCodes.IsRightBraces(ch2))
{
Advance(1);
Expand Down Expand Up @@ -2434,11 +2450,15 @@ private TomlLocalDateTime ReadLocalDateTime(ReadOnlySpan<byte> bytes)
if (bytes.Length > TomlCodes.DateTime.LocalDateTimeFormatLength)
{
if (!TomlCodes.IsDot(Unsafe.Add(ref refBytes, 19))) ExceptionHelper.ThrowIncorrectTomlLocalDateTimeFormat();

var dot = true;
var index = 20;
while (index < bytes.Length)
{
if (!TomlCodes.IsNumber(Unsafe.Add(ref refBytes, index++))) ExceptionHelper.ThrowIncorrectTomlLocalDateTimeFormat();
dot = false;
}
if (dot) ExceptionHelper.ThrowIncorrectTomlLocalDateTimeFormat();
}

return TomlLocalDateTime.Parse(bytes);
Expand Down Expand Up @@ -2520,10 +2540,13 @@ private TomlLocalTime ReadLocalTime(ReadOnlySpan<byte> bytes)
{
if (!TomlCodes.IsDot(Unsafe.Add(ref refBytes, 5))) ExceptionHelper.ThrowIncorrectTomlLocalTimeFormat();
var index = 6;
var dot = true;
while (index < bytes.Length)
{
if (!TomlCodes.IsNumber(Unsafe.Add(ref refBytes, index++))) ExceptionHelper.ThrowIncorrectTomlLocalTimeFormat();
dot = false;
}
if (dot) ExceptionHelper.ThrowIncorrectTomlLocalTimeFormat();
}

return TomlLocalTime.ParseToOmitSeconds(bytes);
Expand Down Expand Up @@ -2559,12 +2582,15 @@ private TomlLocalTime ReadLocalTime(ReadOnlySpan<byte> bytes)
{
if (!TomlCodes.IsDot(Unsafe.Add(ref refBytes, 8))) ExceptionHelper.ThrowIncorrectTomlLocalTimeFormat();
var index = 9;
var dot = true;
while (index < bytes.Length)
{
if (!TomlCodes.IsNumber(Unsafe.Add(ref refBytes, index++))) ExceptionHelper.ThrowIncorrectTomlLocalTimeFormat();
dot = false;
}
}

if (dot) ExceptionHelper.ThrowIncorrectTomlLocalTimeFormat();
}
return TomlLocalTime.Parse(bytes);
}
}
Expand Down Expand Up @@ -2600,6 +2626,21 @@ private TomlOffsetDateTime ReadOffsetDateTime(ReadOnlySpan<byte> bytes)
if (!TomlCodes.IsNumber(bytes[17])) ExceptionHelper.ThrowIncorrectTomlOffsetDateTimeFormat();
if (!TomlCodes.IsNumber(bytes[18])) ExceptionHelper.ThrowIncorrectTomlOffsetDateTimeFormat();

// YYYY-MM-DDTHH:MM:SS.ssssZ is valid, but YYYY-MM-DDTHH:MM:SS.Z is not.
if (TomlCodes.IsDot(bytes[19]))
{
var index = 20;
var dot = true;
while (index < bytes.Length - 1)
{
if (!TomlCodes.IsNumber(bytes[index++]))
ExceptionHelper.ThrowIncorrectTomlOffsetDateTimeFormat();

dot = false;
}
if (dot) ExceptionHelper.ThrowIncorrectTomlOffsetDateTimeFormat();
}

return TomlOffsetDateTime.Parse(bytes);
}

Expand Down
52 changes: 45 additions & 7 deletions src/CsToml/CsTomlSerializerOptions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

using CsToml.Formatter.Resolver;
using CsToml.Formatter.Resolver;

namespace CsToml;

Expand Down Expand Up @@ -34,7 +33,7 @@ public record CsTomlSerializerOptions(ITomlValueFormatterResolver Resolver)

public SerializeOptions SerializeOptions { get; init; } = new();

public TomlSpec Spec { get; init; } = new();
public TomlSpec Spec { get; init; } = TomlSpec.Version100;
}

public record SerializeOptions
Expand All @@ -48,19 +47,58 @@ public record SerializeOptions

public record TomlSpec
{
#region "TOML v1.1.0 Preview Feature"

public bool AllowUnicodeInBareKeys { get; init; }

/// <summary>
/// TOML v1.0.0 Specification
/// </summary>
public static readonly TomlSpec Version100 = new() {};

/// <summary>
/// TOML v1.1.0 Specification
/// </summary>
public static readonly TomlSpec Version110 = new() {
AllowNewlinesInInlineTables = true,
AllowTrailingCommaInInlineTables = true,
SupportsEscapeSequenceE = true,
AllowSecondsOmissionInTime = true,
SupportsEscapeSequenceX = true,
};

#region "TOML v1.1.0"

/// <summary>
/// Gets a value indicating whether newlines are permitted within TOML inline tables.
/// </summary>
public bool AllowNewlinesInInlineTables { get; init; }

/// <summary>
/// Gets a value indicating whether a trailing comma is permitted in TOML inline tables.
/// </summary>
public bool AllowTrailingCommaInInlineTables { get; init; }

/// <summary>
/// Gets a value indicating whether the escape sequence 'E' is supported by the current implementation.
/// </summary>
public bool SupportsEscapeSequenceE { get; init; }

/// <summary>
/// Gets a value indicating whether time values are allowed to omit seconds when formatted or parsed.
/// </summary>
public bool AllowSecondsOmissionInTime { get; init; }

/// <summary>
/// Gets a value indicating whether escape sequence X is supported by the current implementation.
/// </summary>
public bool SupportsEscapeSequenceX { get; init; }

#endregion

#region "TOML unofficial feature"

/// <summary>
/// <para>Gets a value indicating whether Unicode characters are allowed in bare keys.</para>
/// <para>This is an unofficial extension to the TOML specification and may not be supported by all TOML parsers.</para>
/// </summary>
public bool AllowUnicodeInBareKeys { get; init; }

#endregion
}
7 changes: 7 additions & 0 deletions src/CsToml/Error/ExceptionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,13 @@ internal static void ThrowDotIsUsedAtTheEnd()
internal static void ThrowKeysAreNotJoinedByDots()
{
ThrowException($@"Keys are not joined by dots.");
}

[DoesNotReturn]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ThrowKeyisNotSpecifiedAfterDot()
{
ThrowException($@"Key is not specified after dot.");
}

[DoesNotReturn]
Expand Down
Loading
Loading