diff --git a/src/Tomlyn.Tests/AssertHelper.cs b/src/Tomlyn.Tests/AssertHelper.cs new file mode 100644 index 0000000..11248a4 --- /dev/null +++ b/src/Tomlyn.Tests/AssertHelper.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019 - Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using NUnit.Framework; + +namespace Tomlyn.Tests +{ + public static class AssertHelper + { + public static void AreEqualNormalizeNewLine(string expected, string actual, string message = null) + { + expected = NormalizeEndOfLine(expected); + actual = NormalizeEndOfLine(actual); + Assert.AreEqual(expected, actual, message); + } + + public static string NormalizeEndOfLine(string text) + { + return text.Replace("\r\n", "\n"); + } + } +} \ No newline at end of file diff --git a/src/Tomlyn.Tests/ModelTests.cs b/src/Tomlyn.Tests/ModelTests.cs index 41dc826..7bc6daa 100644 --- a/src/Tomlyn.Tests/ModelTests.cs +++ b/src/Tomlyn.Tests/ModelTests.cs @@ -250,20 +250,12 @@ private static void AssertJson(string input, string expectedJson) var serializer = JsonSerializer.Create(new JsonSerializerSettings() { Formatting = Formatting.Indented }); var writer = new StringWriter(); serializer.Serialize(writer, model); - var jsonResult = NormalizeEndOfLine(writer.ToString()); - expectedJson = NormalizeEndOfLine(expectedJson); + var jsonResult = writer.ToString(); StandardTests.DisplayHeader("json"); Console.WriteLine(jsonResult); - Assert.AreEqual(expectedJson, jsonResult); + AssertHelper.AreEqualNormalizeNewLine(expectedJson, jsonResult); } - - private static string NormalizeEndOfLine(string text) - { - return text.Replace("\r\n", "\n"); - } - - } } \ No newline at end of file diff --git a/src/Tomlyn.Tests/StandardTests.cs b/src/Tomlyn.Tests/StandardTests.cs index c314fc7..111f991 100644 --- a/src/Tomlyn.Tests/StandardTests.cs +++ b/src/Tomlyn.Tests/StandardTests.cs @@ -119,14 +119,14 @@ public static IEnumerable ListTomlFiles(string type) { var functionName = Path.GetFileName(file); - var input = File.ReadAllText(file); + var input = File.ReadAllText(file, Encoding.UTF8); string json = null; if (type == "valid") { var jsonFile = Path.ChangeExtension(file, "json"); Assert.True(File.Exists(jsonFile), $"The json file `{jsonFile}` does not exist"); - json = File.ReadAllText(jsonFile); + json = File.ReadAllText(jsonFile, Encoding.UTF8); } tests.Add(new TestCaseData(functionName, input, json)); } diff --git a/src/Tomlyn.Tests/SyntaxTests.cs b/src/Tomlyn.Tests/SyntaxTests.cs new file mode 100644 index 0000000..00ad27c --- /dev/null +++ b/src/Tomlyn.Tests/SyntaxTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2019 - Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using NUnit.Framework; +using Tomlyn.Syntax; + +namespace Tomlyn.Tests +{ + public class SyntaxTests + { + [Test] + public void TestDocument() + { + var doc = new DocumentSyntax() + { + Tables = + { + new TableSyntax("test") + { + Items = + { + {"a", 1}, + {"b", true }, + {"c", "Check"}, + {"d", "ToEscape\nWithAnotherChar\t" }, + {"e", 12.5 }, + {"f", new int[] {1,2,3,4} }, + {"g", new string[] {"0", "1", "2"} }, + {"key with space", 2} + } + } + } + }; + + var docStr = doc.ToString(); + + var expected = @"[test] +a = 1 +b = true +c = ""Check"" +d = ""ToEscape\nWithAnotherChar\t"" +e = 12.5 +f = [1, 2, 3, 4] +g = [""0"", ""1"", ""2""] +""key with space"" = 2 +"; + + AssertHelper.AreEqualNormalizeNewLine(expected, docStr); + + // Reparse the result and compare it again + var newDoc = Toml.Parse(docStr); + AssertHelper.AreEqualNormalizeNewLine(expected, newDoc.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Tomlyn.sln.DotSettings b/src/Tomlyn.sln.DotSettings index 25da38b..39dea03 100644 --- a/src/Tomlyn.sln.DotSettings +++ b/src/Tomlyn.sln.DotSettings @@ -1,7 +1,10 @@  - Copyright (c) $CURRENT_YEAR$ - Alexandre Mutel. All rights reserved. + Copyright (c) Alexandre Mutel. All rights reserved. Licensed under the BSD-Clause 2 license. See license.txt file in the project root for full license information. RFC + True + True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/src/Tomlyn/Model/ObjectKind.cs b/src/Tomlyn/Model/ObjectKind.cs index bdfadaf..8ac757f 100644 --- a/src/Tomlyn/Model/ObjectKind.cs +++ b/src/Tomlyn/Model/ObjectKind.cs @@ -3,6 +3,9 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Model { + /// + /// Kind of an TOML object. + /// public enum ObjectKind { Table, diff --git a/src/Tomlyn/Model/SyntaxTransform.cs b/src/Tomlyn/Model/SyntaxTransform.cs index 21b08fb..47dfbf9 100644 --- a/src/Tomlyn/Model/SyntaxTransform.cs +++ b/src/Tomlyn/Model/SyntaxTransform.cs @@ -7,6 +7,9 @@ namespace Tomlyn.Model { + /// + /// Internal class used to transform a into a + /// internal class SyntaxTransform : SyntaxVisitor { private readonly TomlTable _rootTable; @@ -42,12 +45,12 @@ public override void Visit(TableArraySyntax table) private TomlTable SetKeyValue(KeySyntax key, object value, SyntaxKind kind) { var currentTable = _currentTable; - var name = GetStringFromBasic(key.Base); - var items = key.DotKeyItems; + var name = GetStringFromBasic(key.Key); + var items = key.DotKeys; for (int i = 0; i < items.ChildrenCount; i++) { currentTable = GetTable(currentTable, name, false); - name = GetStringFromBasic(items.GetChildren(i).Value); + name = GetStringFromBasic(items.GetChildren(i).Key); } var isTableArray = kind == SyntaxKind.TableArray; @@ -93,9 +96,9 @@ private TomlTable GetTable(TomlTable table, string key, bool createTableArrayIte return newTable; } - private string GetStringFromBasic(BasicValueSyntax value) + private string GetStringFromBasic(BareKeyOrStringValueSyntax value) { - if (value is BasicKeySyntax basicKey) + if (value is BareKeySyntax basicKey) { return basicKey.Key.Text; } diff --git a/src/Tomlyn/Model/TomlArray.cs b/src/Tomlyn/Model/TomlArray.cs index 022bca9..83bb87a 100644 --- a/src/Tomlyn/Model/TomlArray.cs +++ b/src/Tomlyn/Model/TomlArray.cs @@ -8,7 +8,10 @@ namespace Tomlyn.Model { - public class TomlArray : TomlObject, IList + /// + /// Runtime representation of a TOML array + /// + public sealed class TomlArray : TomlObject, IList { private readonly List _items; diff --git a/src/Tomlyn/Model/TomlBoolean.cs b/src/Tomlyn/Model/TomlBoolean.cs index 5702ed2..fb0c713 100644 --- a/src/Tomlyn/Model/TomlBoolean.cs +++ b/src/Tomlyn/Model/TomlBoolean.cs @@ -3,6 +3,9 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Model { + /// + /// Runtime representation of a TOML bool + /// public sealed class TomlBoolean : TomlValue { public TomlBoolean(bool value) : base(ObjectKind.Boolean, value) diff --git a/src/Tomlyn/Model/TomlDateTime.cs b/src/Tomlyn/Model/TomlDateTime.cs index 1010418..ef80b52 100644 --- a/src/Tomlyn/Model/TomlDateTime.cs +++ b/src/Tomlyn/Model/TomlDateTime.cs @@ -7,6 +7,9 @@ namespace Tomlyn.Model { + /// + /// Runtime representation of a TOML datetime + /// public sealed class TomlDateTime : TomlValue { public TomlDateTime(ObjectKind kind, DateTime value) : base(CheckDateTimeKind(kind), value) diff --git a/src/Tomlyn/Model/TomlFloat.cs b/src/Tomlyn/Model/TomlFloat.cs index a4e92c4..cdd2b93 100644 --- a/src/Tomlyn/Model/TomlFloat.cs +++ b/src/Tomlyn/Model/TomlFloat.cs @@ -3,6 +3,9 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Model { + /// + /// Runtime representation of a TOML float + /// public sealed class TomlFloat : TomlValue { public TomlFloat(double value) : base(ObjectKind.Float, value) diff --git a/src/Tomlyn/Model/TomlInteger.cs b/src/Tomlyn/Model/TomlInteger.cs index 3537de7..5485937 100644 --- a/src/Tomlyn/Model/TomlInteger.cs +++ b/src/Tomlyn/Model/TomlInteger.cs @@ -3,6 +3,9 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Model { + /// + /// Runtime representation of a TOML integer + /// public sealed class TomlInteger : TomlValue { public TomlInteger(long value) : base(ObjectKind.Integer, value) diff --git a/src/Tomlyn/Model/TomlObject.cs b/src/Tomlyn/Model/TomlObject.cs index 7f4bf34..d33bb4a 100644 --- a/src/Tomlyn/Model/TomlObject.cs +++ b/src/Tomlyn/Model/TomlObject.cs @@ -1,24 +1,22 @@ using System; -using Tomlyn.Syntax; namespace Tomlyn.Model { + /// + /// Base class for the runtime representation of a TOML object + /// public abstract class TomlObject { - protected TomlObject(ObjectKind kind) + internal TomlObject(ObjectKind kind) { Kind = kind; } + /// + /// The kind of the object + /// public ObjectKind Kind { get; } - public SyntaxNode Node { get; internal set; } - - internal static bool IsContainer(TomlObject tomlObj) - { - return tomlObj.Kind == ObjectKind.Array || tomlObj.Kind == ObjectKind.Table || tomlObj.Kind == ObjectKind.TableArray; - } - internal static object ToObject(TomlObject tomlObj) { return tomlObj is TomlValue value ? value.ValueAsObject : tomlObj; diff --git a/src/Tomlyn/Model/TomlString.cs b/src/Tomlyn/Model/TomlString.cs index 29c2efb..4434d2b 100644 --- a/src/Tomlyn/Model/TomlString.cs +++ b/src/Tomlyn/Model/TomlString.cs @@ -3,6 +3,9 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Model { + /// + /// Runtime representation of a TOML string + /// public sealed class TomlString : TomlValue { public TomlString(string value) : base(ObjectKind.String, value) diff --git a/src/Tomlyn/Model/TomlTable.cs b/src/Tomlyn/Model/TomlTable.cs index 2b6ae83..0ee5175 100644 --- a/src/Tomlyn/Model/TomlTable.cs +++ b/src/Tomlyn/Model/TomlTable.cs @@ -9,12 +9,21 @@ namespace Tomlyn.Model { - public class TomlTable : TomlObject, IDictionary + /// + /// Runtime representation of a TOML table + /// + /// + /// This object keep the order of the inserted key=values + /// + public sealed class TomlTable : TomlObject, IDictionary { // TODO: optimize the internal by avoiding two structures private readonly List> _order; private readonly Dictionary _map; + /// + /// Creates an instance of a + /// public TomlTable() : base(ObjectKind.Table) { _order = new List>(); diff --git a/src/Tomlyn/Model/TomlTableArray.cs b/src/Tomlyn/Model/TomlTableArray.cs index 7344ca9..4ae008a 100644 --- a/src/Tomlyn/Model/TomlTableArray.cs +++ b/src/Tomlyn/Model/TomlTableArray.cs @@ -8,7 +8,10 @@ namespace Tomlyn.Model { - public class TomlTableArray : TomlObject, IList + /// + /// Runtime representation of a TOML table array + /// + public sealed class TomlTableArray : TomlObject, IList { private readonly List _items; diff --git a/src/Tomlyn/Model/TomlValue.cs b/src/Tomlyn/Model/TomlValue.cs index 2036ccd..665870d 100644 --- a/src/Tomlyn/Model/TomlValue.cs +++ b/src/Tomlyn/Model/TomlValue.cs @@ -6,6 +6,9 @@ namespace Tomlyn.Model { + /// + /// Base class of a TOML value (bool, string, integer, float, datetime) + /// public abstract class TomlValue : TomlObject { internal TomlValue(ObjectKind kind) : base(kind) @@ -15,6 +18,9 @@ internal TomlValue(ObjectKind kind) : base(kind) public abstract object ValueAsObject { get; } } + /// + /// Base class of a TOML value (bool, string, integer, float, datetime) + /// public abstract class TomlValue : TomlValue, IEquatable> where T : IEquatable { private T _value; diff --git a/src/Tomlyn/Parsing/Lexer.cs b/src/Tomlyn/Parsing/Lexer.cs index da80769..f6a8eaa 100644 --- a/src/Tomlyn/Parsing/Lexer.cs +++ b/src/Tomlyn/Parsing/Lexer.cs @@ -155,7 +155,7 @@ private void NextTokenForKey() ReadStringLiteral(start, false); break; case Eof: - _token = SyntaxTokenValue.Eof; + _token = new SyntaxTokenValue(TokenKind.Eof, _position, _position); break; default: // Eat any whitespace @@ -229,7 +229,7 @@ private void NextTokenForValue() ReadStringLiteral(start, true); break; case Eof: - _token = SyntaxTokenValue.Eof; + _token = new SyntaxTokenValue(TokenKind.Eof, _position, _position); break; default: // Eat any whitespace @@ -576,7 +576,20 @@ private void ReadNumberOrDate(char32? signPrefix = null, TextPosition? signPrefi } else { - AddError($"Unable to parse the date time/offset `{dateTimeAsString}`", start, end); + // Try to recover the date using the standard C# (not necessarily RFC3339) + if (DateTime.TryParse(dateTimeAsString, CultureInfo.InvariantCulture, DateTimeStyles.AllowInnerWhite, out datetime)) + { + _token = new SyntaxTokenValue(TokenKind.LocalDateTime, start, end, datetime); + + // But we produce an error anyway + AddError($"Invalid format of date time/offset `{dateTimeAsString}` not following RFC3339", start, end); + } + else + { + _token = new SyntaxTokenValue(TokenKind.LocalDateTime, start, end, new DateTime()); + // But we produce an error anyway + AddError($"Unable to parse the date time/offset `{dateTimeAsString}`", start, end); + } } return; @@ -685,7 +698,7 @@ private void ReadDigits(ref TextPosition end, bool isPreviousDigit) if (!isPreviousDigit) { - AddError("An underscore `_` must not followed a digit", _position, _position); + AddError("Missing a digit after a trailing underscore `_`", _position, _position); } } @@ -1074,7 +1087,8 @@ private char32 NextCharFromReader() private void CheckCharacter(char32 c) { - if (!CharHelper.IsValidUnicodeScalarValue(c)) + // The character 0xFFFD is the replacement character and we assume that something went wrong when reading the input + if (!CharHelper.IsValidUnicodeScalarValue(c) || c == 0xFFFD) { AddError($"The character `{c}` is an invalid UTF8 character", _current.Position, _current.Position); } diff --git a/src/Tomlyn/Parsing/Parser.cs b/src/Tomlyn/Parsing/Parser.cs index 3e79234..0eaa278 100644 --- a/src/Tomlyn/Parsing/Parser.cs +++ b/src/Tomlyn/Parsing/Parser.cs @@ -95,10 +95,10 @@ private static void AddToListAndUpdateSpan(SyntaxList list.Span.End = node.Span.End; } - list.AddChildren(node); + list.Add(node); } - private bool TryParseTableEntry(out TableEntrySyntax nextEntry) + private bool TryParseTableEntry(out SyntaxNode nextEntry) { nextEntry = null; while (true) @@ -223,6 +223,35 @@ private ValueSyntax ParseValue() return null; } + private bool IsCurrentValue() + { + switch (_token.Kind) + { + case TokenKind.Integer: + case TokenKind.IntegerHexa: + case TokenKind.IntegerOctal: + case TokenKind.IntegerBinary: + case TokenKind.Infinite: + case TokenKind.PositiveInfinite: + case TokenKind.NegativeInfinite: + case TokenKind.Float: + case TokenKind.String: + case TokenKind.StringMulti: + case TokenKind.StringLiteral: + case TokenKind.StringLiteralMulti: + case TokenKind.OpenBracket: + case TokenKind.OpenBrace: + case TokenKind.OffsetDateTime: + case TokenKind.LocalDateTime: + case TokenKind.LocalDate: + case TokenKind.LocalTime: + case TokenKind.True: + case TokenKind.False: + return true; + } + return false; + } + private BooleanValueSyntax ParseBoolean() { var boolean = Open(); @@ -302,6 +331,10 @@ private ArraySyntax ParseArray() { item.Comma = EatToken(); } + else if (IsCurrentValue()) + { + LogError($"Missing a `,` (token: comma) to separate items in an array"); + } else { expectingEndOfArray = true; @@ -312,7 +345,7 @@ private ArraySyntax ParseArray() } else { - LogError($"Unexpected token `{_token.Kind}`. Expecting a closing ] for an array"); + LogError($"Unexpected token `{ToPrintable(_token)}` (token: `{_token.Kind}`). Expecting a closing `]` for an array"); break; } } @@ -419,15 +452,15 @@ private TableSyntaxBase ParseTableOrTableArray() private KeySyntax ParseKey() { var key = Open(); - key.Base = ParseBaseKey(); + key.Key = ParseBaseKey(); while (_token.Kind == TokenKind.Dot) { - AddToListAndUpdateSpan(key.DotKeyItems, ParseDotKey()); + AddToListAndUpdateSpan(key.DotKeys, ParseDotKey()); } return Close(key); } - private BasicValueSyntax ParseBaseKey() + private BareKeyOrStringValueSyntax ParseBaseKey() { if (_token.Kind == TokenKind.BasicKey) { @@ -455,13 +488,13 @@ private DottedKeyItemSyntax ParseDotKey() { var dotKey = Open(); dotKey.Dot = EatToken(); - dotKey.Value = ParseBaseKey(); + dotKey.Key = ParseBaseKey(); return Close(dotKey); } - private BasicKeySyntax ParseBasicKey() + private BareKeySyntax ParseBasicKey() { - var basicKey = Open(); + var basicKey = Open(); basicKey.Key = EatToken(TokenKind.BasicKey); return Close(basicKey); } @@ -479,13 +512,15 @@ private SyntaxToken EatToken(TokenKind tokenKind) var invalid = Open(); invalid.InvalidKind = _token.Kind; syntax = invalid; + var tokenText = tokenKind.ToText(); + var expectingTokenText = tokenText != null ? $"while expecting `{tokenText}` (token: `{tokenKind.ToString().ToLowerInvariant()}`)" : $"while expecting token `{tokenKind.ToString().ToLowerInvariant()}`"; if (_token.Kind == TokenKind.Invalid) { - LogError($"Unexpected token found `{ToPrintable(_token)}` while expecting `{tokenKind.ToText() ?? ToPrintable(_token)}` (token: `{tokenKind.ToString().ToLowerInvariant()}`)"); + LogError($"Unexpected token found `{ToPrintable(_token)}` {expectingTokenText}"); } else { - LogError($"Unexpected token found `{ToPrintable(_token)}` (token: `{_token.Kind.ToString().ToLowerInvariant()}`) while expecting `{tokenKind.ToText() ?? ToPrintable(_token)}` (token: `{tokenKind.ToString().ToLowerInvariant()}`)"); + LogError($"Unexpected token found `{ToPrintable(_token)}` (token: `{_token.Kind.ToString().ToLowerInvariant()}`) {expectingTokenText}"); } } syntax.TokenKind = tokenKind; @@ -590,7 +625,7 @@ private void NextToken() _currentTrivias.Add(new SyntaxTrivia { Span = new SourceSpan(_lexer.Source.SourcePath, _lexer.Token.Start, _lexer.Token.End), Kind = _lexer.Token.Kind, Text = _lexer.Token.GetText(_lexer.Source) }); } - _token = result ? _lexer.Token : SyntaxTokenValue.Eof; + _token = result ? _lexer.Token : new SyntaxTokenValue(TokenKind.Eof, new TextPosition(), new TextPosition()); } private bool IsHidden(TokenKind tokenKind) diff --git a/src/Tomlyn/Parsing/SyntaxTokenValue.cs b/src/Tomlyn/Parsing/SyntaxTokenValue.cs index dba2302..cffec4f 100644 --- a/src/Tomlyn/Parsing/SyntaxTokenValue.cs +++ b/src/Tomlyn/Parsing/SyntaxTokenValue.cs @@ -13,8 +13,6 @@ namespace Tomlyn.Parsing /// internal readonly struct SyntaxTokenValue : IEquatable { - public static readonly SyntaxTokenValue Eof = new SyntaxTokenValue(TokenKind.Eof, TextPosition.Eof, TextPosition.Eof); - /// /// Initializes a new instance of the struct. /// diff --git a/src/Tomlyn/Syntax/ArrayItemSyntax.cs b/src/Tomlyn/Syntax/ArrayItemSyntax.cs index b1f5cd8..1656c12 100644 --- a/src/Tomlyn/Syntax/ArrayItemSyntax.cs +++ b/src/Tomlyn/Syntax/ArrayItemSyntax.cs @@ -3,21 +3,33 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Syntax { + /// + /// An item of an + /// public sealed class ArrayItemSyntax : SyntaxNode { private ValueSyntax _value; private SyntaxToken _comma; + /// + /// Creates an instance of + /// public ArrayItemSyntax() : base(SyntaxKind.ArrayItem) { } + /// + /// Gets or sets the value of this item. + /// public ValueSyntax Value { get => _value; set => ParentToThis(ref _value, value); } + /// + /// Gets or sets the comma of this item (mandatory to separate elements in an array) + /// public SyntaxToken Comma { get => _comma; diff --git a/src/Tomlyn/Syntax/ArraySyntax.cs b/src/Tomlyn/Syntax/ArraySyntax.cs index c187f28..f3b85f1 100644 --- a/src/Tomlyn/Syntax/ArraySyntax.cs +++ b/src/Tomlyn/Syntax/ArraySyntax.cs @@ -1,26 +1,86 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System; + namespace Tomlyn.Syntax { + /// + /// An array TOML node. + /// public sealed class ArraySyntax : ValueSyntax { private SyntaxToken _openBracket; private SyntaxToken _closeBracket; + /// + /// Creates an instance of an + /// public ArraySyntax() : base(SyntaxKind.Array) { Items = new SyntaxList() { Parent = this }; } + /// + /// Creates an instance of an + /// + /// An array of integer values + public ArraySyntax(int[] values) : this() + { + if (values == null) throw new ArgumentNullException(nameof(values)); + OpenBracket = SyntaxFactory.Token(TokenKind.OpenBracket); + CloseBracket = SyntaxFactory.Token(TokenKind.CloseBracket); + for (int i = 0; i < values.Length; i++) + { + var item = new ArrayItemSyntax {Value = new IntegerValueSyntax(values[i])}; + if (i + 1 < values.Length) + { + item.Comma = SyntaxFactory.Token(TokenKind.Comma); + item.Comma.AddTrailingWhitespace(); + } + Items.Add(item); + } + } + + /// + /// Creates an instance of an + /// + /// An array of string values + public ArraySyntax(string[] values) : this() + { + if (values == null) throw new ArgumentNullException(nameof(values)); + OpenBracket = SyntaxFactory.Token(TokenKind.OpenBracket); + CloseBracket = SyntaxFactory.Token(TokenKind.CloseBracket); + for (int i = 0; i < values.Length; i++) + { + var item = new ArrayItemSyntax { Value = new StringValueSyntax(values[i]) }; + if (i + 1 < values.Length) + { + item.Comma = SyntaxFactory.Token(TokenKind.Comma); + item.Comma.AddTrailingWhitespace(); + } + Items.Add(item); + } + } + + /// + /// Gets or sets the open bracket `[` token + /// public SyntaxToken OpenBracket { get => _openBracket; set => ParentToThis(ref _openBracket, value, TokenKind.OpenBracket); } + /// + /// Gets the of this array. + /// public SyntaxList Items { get; } + /// + /// Gets or sets the close bracket `]` token + /// public SyntaxToken CloseBracket { get => _closeBracket; diff --git a/src/Tomlyn/Syntax/BareKeyOrStringValueSyntax.cs b/src/Tomlyn/Syntax/BareKeyOrStringValueSyntax.cs new file mode 100644 index 0000000..df7cb91 --- /dev/null +++ b/src/Tomlyn/Syntax/BareKeyOrStringValueSyntax.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2019 - Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. +namespace Tomlyn.Syntax +{ + /// + /// Base class for a or a + /// + public abstract class BareKeyOrStringValueSyntax : ValueSyntax + { + internal BareKeyOrStringValueSyntax(SyntaxKind kind) : base(kind) + { + } + } +} \ No newline at end of file diff --git a/src/Tomlyn/Syntax/BareKeySyntax.cs b/src/Tomlyn/Syntax/BareKeySyntax.cs new file mode 100644 index 0000000..77b5947 --- /dev/null +++ b/src/Tomlyn/Syntax/BareKeySyntax.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2019 - Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System; +using Tomlyn.Text; + +namespace Tomlyn.Syntax +{ + /// + /// A TOML bare key syntax node. + /// + public sealed class BareKeySyntax : BareKeyOrStringValueSyntax + { + private SyntaxToken _key; + + /// + /// Creates a new instance of a + /// + public BareKeySyntax() : base(SyntaxKind.BasicKey) + { + } + + /// + /// Creates a new instance of a + /// + /// The name used for this key + public BareKeySyntax(string name) : this() + { + if (!IsBareKey(name)) throw new ArgumentOutOfRangeException($"The key `{name}` does not contain valid characters [A-Za-z0-9_\\-]"); + Key = new SyntaxToken(TokenKind.BasicKey, name); + } + + /// + /// A textual representation of the key + /// + public SyntaxToken Key + { + get => _key; + set => ParentToThis(ref _key, value, TokenKind.BasicKey); + } + + public override int ChildrenCount => 1; + + public override void Accept(SyntaxVisitor visitor) + { + visitor.Visit(this); + } + + protected override SyntaxNode GetChildrenImpl(int index) + { + return Key; + } + + public static bool IsBareKey(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (name.Length == 0 || string.IsNullOrWhiteSpace(name)) return false; + foreach (var c in name) + { + if (!CharHelper.IsKeyContinue(c)) + { + return false; + } + } + return true; + } + } +} \ No newline at end of file diff --git a/src/Tomlyn/Syntax/BasicKeySyntax.cs b/src/Tomlyn/Syntax/BasicKeySyntax.cs deleted file mode 100644 index a504c3f..0000000 --- a/src/Tomlyn/Syntax/BasicKeySyntax.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2019 - Alexandre Mutel. All rights reserved. -// Licensed under the BSD-Clause 2 license. -// See license.txt file in the project root for full license information. -namespace Tomlyn.Syntax -{ - public sealed class BasicKeySyntax : BasicValueSyntax - { - private SyntaxToken _key; - - public BasicKeySyntax() : base(SyntaxKind.BasicKey) - { - } - - public SyntaxToken Key - { - get => _key; - set => ParentToThis(ref _key, value, TokenKind.BasicKey); - } - - public override int ChildrenCount => 1; - - public override void Accept(SyntaxVisitor visitor) - { - visitor.Visit(this); - } - - protected override SyntaxNode GetChildrenImpl(int index) - { - return Key; - } - } -} \ No newline at end of file diff --git a/src/Tomlyn/Syntax/BasicValueSyntax.cs b/src/Tomlyn/Syntax/BasicValueSyntax.cs deleted file mode 100644 index 7faffee..0000000 --- a/src/Tomlyn/Syntax/BasicValueSyntax.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2019 - Alexandre Mutel. All rights reserved. -// Licensed under the BSD-Clause 2 license. -// See license.txt file in the project root for full license information. -namespace Tomlyn.Syntax -{ - public abstract class BasicValueSyntax : ValueSyntax - { - protected BasicValueSyntax(SyntaxKind kind) : base(kind) - { - } - } -} \ No newline at end of file diff --git a/src/Tomlyn/Syntax/BooleanValueSyntax.cs b/src/Tomlyn/Syntax/BooleanValueSyntax.cs index 90d147d..744a1a4 100644 --- a/src/Tomlyn/Syntax/BooleanValueSyntax.cs +++ b/src/Tomlyn/Syntax/BooleanValueSyntax.cs @@ -3,49 +3,47 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Syntax { + /// + /// A boolean TOML value syntax node. + /// public sealed class BooleanValueSyntax : ValueSyntax { private SyntaxToken _token; private bool _value; + /// + /// Creates an instance of a + /// public BooleanValueSyntax() : base(SyntaxKind.Boolean) { } + /// + /// Creates an instance of a + /// + /// The boolean value + public BooleanValueSyntax(bool value) : this() + { + var kind = value ? TokenKind.True : TokenKind.False; + Token = new SyntaxToken(kind, kind.ToText()); + } + + /// + /// The boolean token value (true or false) + /// public SyntaxToken Token { get => _token; - set - { - ParentToThis(ref _token, value, TokenKind.True, TokenKind.False); - // Update the value accordingly - if (_token != null) - { - _value = _token.TokenKind == TokenKind.True; - } - else - { - _value = false; - } - } + set { ParentToThis(ref _token, value, TokenKind.True, TokenKind.False); } } + /// + /// The boolean parsed value. + /// public bool Value { get => _value; - set - { - // Update the token kind accordingly - if (_token != null) - { - _token.TokenKind = value ? TokenKind.True : TokenKind.False; - } - else - { - value = false; - } - _value = value; - } + set => _value = value; } public override void Accept(SyntaxVisitor visitor) diff --git a/src/Tomlyn/Syntax/DateTimeValueSyntax.cs b/src/Tomlyn/Syntax/DateTimeValueSyntax.cs index b5ee309..e4b57fc 100644 --- a/src/Tomlyn/Syntax/DateTimeValueSyntax.cs +++ b/src/Tomlyn/Syntax/DateTimeValueSyntax.cs @@ -3,17 +3,64 @@ // See license.txt file in the project root for full license information. using System; +using System.Globalization; namespace Tomlyn.Syntax { + /// + /// A datetime TOML value syntax node. + /// public sealed class DateTimeValueSyntax : ValueSyntax { + private SyntaxToken _token; + + /// + /// Creates an instance of + /// + /// The kind of datetime public DateTimeValueSyntax(SyntaxKind kind) : base(CheckDateTimeKind(kind)) { } - public SyntaxToken Token { get; set; } + /// + /// Creates a new instance of + /// + /// + /// + public DateTimeValueSyntax(SyntaxKind kind, DateTime value) : base(CheckDateTimeKind(kind)) + { + TokenKind tokenKind = 0; + switch (kind) + { + case SyntaxKind.OffsetDateTime: + tokenKind = TokenKind.OffsetDateTime; + break; + case SyntaxKind.LocalDateTime: + tokenKind = TokenKind.LocalDateTime; + break; + case SyntaxKind.LocalDate: + tokenKind = TokenKind.LocalDate; + break; + case SyntaxKind.LocalTime: + tokenKind = TokenKind.LocalTime; + break; + } + + Token = new SyntaxToken(tokenKind, ToString(kind, value)); + } + + /// + /// Gets or sets the datetime token. + /// + public SyntaxToken Token + { + get => _token; + set => ParentToThis(ref _token, value, value != null && value.TokenKind.IsDateTime(), $"The token kind `{value?.TokenKind}` is not a datetime token"); + } + /// + /// Gets or sets the parsed datetime value. + /// public DateTime Value { get; set; } public override int ChildrenCount => 1; @@ -41,5 +88,25 @@ private static SyntaxKind CheckDateTimeKind(SyntaxKind kind) throw new ArgumentOutOfRangeException(nameof(kind), kind, null); } } + + internal static string ToString(SyntaxKind kind, DateTime value) + { + switch (kind) + { + case SyntaxKind.OffsetDateTime: + var time = value.ToUniversalTime(); + if (time.Millisecond == 0) return time.ToString("yyyy-MM-dd'T'HH:mm:ssK", CultureInfo.InvariantCulture); + return time.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK", CultureInfo.InvariantCulture); + case SyntaxKind.LocalDateTime: + if (value.Millisecond == 0) return value.ToString("yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture); + return value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff", CultureInfo.InvariantCulture); + case SyntaxKind.LocalDate: + return value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + case SyntaxKind.LocalTime: + return value.Millisecond == 0 ? value.ToString("HH:mm:ss", CultureInfo.InvariantCulture) : value.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture); + default: + throw new ArgumentOutOfRangeException(nameof(kind), kind, null); + } + } } } \ No newline at end of file diff --git a/src/Tomlyn/Syntax/DiagnosticMessage.cs b/src/Tomlyn/Syntax/DiagnosticMessage.cs index 7c9f66f..85f2fb3 100644 --- a/src/Tomlyn/Syntax/DiagnosticMessage.cs +++ b/src/Tomlyn/Syntax/DiagnosticMessage.cs @@ -7,8 +7,17 @@ namespace Tomlyn.Syntax { + /// + /// A diagnostic message with errors. + /// public class DiagnosticMessage { + /// + /// Creates a new instance of a + /// + /// The kind of message + /// The source span + /// The message public DiagnosticMessage(DiagnosticMessageKind kind, SourceSpan span, string message) { Kind = kind; @@ -16,11 +25,20 @@ public DiagnosticMessage(DiagnosticMessageKind kind, SourceSpan span, string mes Message = message; } - public DiagnosticMessageKind Kind { get; set; } + /// + /// Gets the kind of message. + /// + public DiagnosticMessageKind Kind { get; } - public SourceSpan Span { get; set; } + /// + /// Gets the source span. + /// + public SourceSpan Span { get; } - public string Message { get; set; } + /// + /// Gets the message. + /// + public string Message { get; } public override string ToString() { diff --git a/src/Tomlyn/Syntax/DiagnosticMessageKind.cs b/src/Tomlyn/Syntax/DiagnosticMessageKind.cs index 99e4107..09203d0 100644 --- a/src/Tomlyn/Syntax/DiagnosticMessageKind.cs +++ b/src/Tomlyn/Syntax/DiagnosticMessageKind.cs @@ -3,10 +3,19 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Syntax { + /// + /// Kind of a + /// public enum DiagnosticMessageKind { + /// + /// An error message. + /// Error, + /// + /// A warning message. + /// Warning, } } \ No newline at end of file diff --git a/src/Tomlyn/Syntax/DiagnosticsBag.cs b/src/Tomlyn/Syntax/DiagnosticsBag.cs index 2c0256d..cab0113 100644 --- a/src/Tomlyn/Syntax/DiagnosticsBag.cs +++ b/src/Tomlyn/Syntax/DiagnosticsBag.cs @@ -9,23 +9,44 @@ namespace Tomlyn.Syntax { + /// + /// A container for + /// [DebuggerDisplay("{Count} Errors: {HasErrors}")] public class DiagnosticsBag : IEnumerable { private readonly List _messages; + /// + /// Creates a new instance of a + /// public DiagnosticsBag() { _messages = new List(); } + /// + /// Gets the number of messages. + /// public int Count => _messages.Count; + /// + /// Gets the message at the specified index. + /// + /// Index of the message. + /// A diagnostic message. public DiagnosticMessage this[int index] => _messages[index]; + /// + /// Gets a boolean indicating if this bag contains any error messages. + /// public bool HasErrors { get; private set; } + /// + /// Adds the specified message to this bag. + /// + /// The message to add public void Add(DiagnosticMessage message) { if (message == null) throw new ArgumentNullException(nameof(message)); @@ -36,22 +57,39 @@ public void Add(DiagnosticMessage message) } } + /// + /// Clear this bag including the error state. + /// public void Clear() { _messages.Clear(); HasErrors = false; } + /// + /// Adds a warning message + /// + /// The source span + /// The warning message public void Warning(SourceSpan span, string text) { Add(new DiagnosticMessage(DiagnosticMessageKind.Warning, span, text)); } + /// + /// Adds an error message + /// + /// The source span + /// The error message public void Error(SourceSpan span, string text) { Add(new DiagnosticMessage(DiagnosticMessageKind.Error, span, text)); } + /// + /// Gets the enumerator of + /// + /// public List.Enumerator GetEnumerator() { return _messages.GetEnumerator(); @@ -77,6 +115,5 @@ public override string ToString() return builder.ToString(); } - } } \ No newline at end of file diff --git a/src/Tomlyn/Syntax/DocumentSyntax.cs b/src/Tomlyn/Syntax/DocumentSyntax.cs index 4c07b27..0549264 100644 --- a/src/Tomlyn/Syntax/DocumentSyntax.cs +++ b/src/Tomlyn/Syntax/DocumentSyntax.cs @@ -3,28 +3,46 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Syntax { + /// + /// Root TOML syntax tree + /// public sealed class DocumentSyntax : SyntaxNode { + /// + /// Creates an instance of a + /// public DocumentSyntax() : base(SyntaxKind.Document) { KeyValues = new SyntaxList() {Parent = this}; Tables = new SyntaxList() { Parent = this }; Diagnostics = new DiagnosticsBag(); } + + /// + /// Gets the diagnostics attached to this document. + /// public DiagnosticsBag Diagnostics { get; } + /// + /// Gets a boolean indicating if the has any errors. + /// public bool HasErrors => Diagnostics.HasErrors; - public override void Accept(SyntaxVisitor visitor) - { - visitor.Visit(this); - } - + /// + /// Gets the list of + /// public SyntaxList KeyValues { get; } - + /// + /// Gets the list of tables (either or ) + /// public SyntaxList Tables { get; } + public override void Accept(SyntaxVisitor visitor) + { + visitor.Visit(this); + } + public override int ChildrenCount => 2; protected override SyntaxNode GetChildrenImpl(int index) diff --git a/src/Tomlyn/Syntax/DottedKeyItemSyntax.cs b/src/Tomlyn/Syntax/DottedKeyItemSyntax.cs index 48d12a9..33b33d4 100644 --- a/src/Tomlyn/Syntax/DottedKeyItemSyntax.cs +++ b/src/Tomlyn/Syntax/DottedKeyItemSyntax.cs @@ -1,27 +1,53 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System; + namespace Tomlyn.Syntax { + /// + /// A part of a TOML dotted key used by + /// public sealed class DottedKeyItemSyntax : ValueSyntax { private SyntaxToken _dot; - private BasicValueSyntax _value; + private BareKeyOrStringValueSyntax _key; + /// + /// Creates an instance of + /// public DottedKeyItemSyntax() : base(SyntaxKind.DottedKeyItem) { } + /// + /// Creates an instance of + /// + /// The key used after the `.` + public DottedKeyItemSyntax(string key) : this() + { + if (key == null) throw new ArgumentNullException(nameof(key)); + Dot = SyntaxFactory.Token(TokenKind.Dot); + Key = BareKeySyntax.IsBareKey(key) ? (BareKeyOrStringValueSyntax)new BareKeySyntax(key) : new StringValueSyntax(key); + } + + /// + /// The token `.` + /// public SyntaxToken Dot { get => _dot; set => ParentToThis(ref _dot, value, TokenKind.Dot); } - public BasicValueSyntax Value + /// + /// The following key or string node. + /// + public BareKeyOrStringValueSyntax Key { - get => _value; - set => ParentToThis(ref _value, value); + get => _key; + set => ParentToThis(ref _key, value); } public override void Accept(SyntaxVisitor visitor) @@ -33,7 +59,7 @@ public override void Accept(SyntaxVisitor visitor) protected override SyntaxNode GetChildrenImpl(int index) { - return index == 0 ? (SyntaxNode)Dot : Value; + return index == 0 ? (SyntaxNode)Dot : Key; } } } \ No newline at end of file diff --git a/src/Tomlyn/Syntax/FloatValueSyntax.cs b/src/Tomlyn/Syntax/FloatValueSyntax.cs index 3c57485..652881f 100644 --- a/src/Tomlyn/Syntax/FloatValueSyntax.cs +++ b/src/Tomlyn/Syntax/FloatValueSyntax.cs @@ -1,22 +1,62 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System.Globalization; + namespace Tomlyn.Syntax { + /// + /// A float TOML value syntax node. + /// public sealed class FloatValueSyntax : ValueSyntax { private SyntaxToken _token; + /// + /// Creates an instance of + /// public FloatValueSyntax() : base(SyntaxKind.Float) { } + /// + /// Creates an instance of + /// + /// The double value + public FloatValueSyntax(double value) : this() + { + if (double.IsNaN(value)) + { + Token = new SyntaxToken(TokenKind.Nan, TokenKind.Nan.ToText()); + } + else if (double.IsPositiveInfinity(value)) + { + Token = new SyntaxToken(TokenKind.PositiveInfinite, TokenKind.PositiveInfinite.ToText()); + } + else if (double.IsNegativeInfinity(value)) + { + Token = new SyntaxToken(TokenKind.NegativeInfinite, TokenKind.NegativeInfinite.ToText()); + } + else + { + Token = new SyntaxToken(TokenKind.Float, value.ToString("G16", CultureInfo.InvariantCulture)); + } + Value = value; + } + + /// + /// The token storing the float value. + /// public SyntaxToken Token { get => _token; set => ParentToThis(ref _token, value, value != null && value.TokenKind.IsFloat(), TokenKind.Float); } + /// + /// The parsed value of the + /// public double Value { get; set; } public override void Accept(SyntaxVisitor visitor) diff --git a/src/Tomlyn/Syntax/InlineTableItemSyntax.cs b/src/Tomlyn/Syntax/InlineTableItemSyntax.cs index 90d681b..07d38aa 100644 --- a/src/Tomlyn/Syntax/InlineTableItemSyntax.cs +++ b/src/Tomlyn/Syntax/InlineTableItemSyntax.cs @@ -1,23 +1,47 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System; + namespace Tomlyn.Syntax { + /// + /// A key-value pair item of an inline table. + /// public sealed class InlineTableItemSyntax : SyntaxNode { private KeyValueSyntax _keyValue; private SyntaxToken _comma; + /// + /// Creates an instance of + /// public InlineTableItemSyntax() : base(SyntaxKind.InlineTable) { } + /// + /// Creates an instance of + /// + /// The key=value + public InlineTableItemSyntax(KeyValueSyntax keyValue) : this() + { + KeyValue = keyValue ?? throw new ArgumentNullException(nameof(keyValue)); + } + + /// + /// Gets or sets the . + /// public KeyValueSyntax KeyValue { get => _keyValue; set => ParentToThis(ref _keyValue, value); } + /// + /// Gets or sets the comma, mandatory to separate entries in an inline table. + /// public SyntaxToken Comma { get => _comma; diff --git a/src/Tomlyn/Syntax/InlineTableSyntax.cs b/src/Tomlyn/Syntax/InlineTableSyntax.cs index b66f1d4..471510b 100644 --- a/src/Tomlyn/Syntax/InlineTableSyntax.cs +++ b/src/Tomlyn/Syntax/InlineTableSyntax.cs @@ -1,26 +1,64 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System; + namespace Tomlyn.Syntax { + /// + /// An inline table TOML syntax node. + /// public sealed class InlineTableSyntax : ValueSyntax { private SyntaxToken _openBrace; private SyntaxToken _closeBrace; + /// + /// Creates a new instance of an + /// public InlineTableSyntax() : base(SyntaxKind.InlineTable) { Items = new SyntaxList() { Parent = this }; } + /// + /// Creates a new instance of an + /// + /// The key values of this inline table + public InlineTableSyntax(params KeyValueSyntax[] keyValues) : this() + { + if (keyValues == null) throw new ArgumentNullException(nameof(keyValues)); + OpenBrace = SyntaxFactory.Token(TokenKind.OpenBrace).AddTrailingWhitespace(); + CloseBrace = SyntaxFactory.Token(TokenKind.CloseBrace).AddLeadingWhitespace(); + + for (var i = 0; i < keyValues.Length; i++) + { + var keyValue = keyValues[i]; + Items.Add(new InlineTableItemSyntax(keyValue) + { + Comma = (i + 1 < keyValues.Length) ? SyntaxFactory.Token(TokenKind.Comma).AddTrailingWhitespace() : null + }); + } + } + + /// + /// The token open brace `{` + /// public SyntaxToken OpenBrace { get => _openBrace; set => ParentToThis(ref _openBrace, value, TokenKind.OpenBrace); } + /// + /// The items of this table. + /// public SyntaxList Items { get; } - + + /// + /// The token close brace `}` + /// public SyntaxToken CloseBrace { get => _closeBrace; diff --git a/src/Tomlyn/Syntax/IntegerValueSyntax.cs b/src/Tomlyn/Syntax/IntegerValueSyntax.cs index 2919571..fa748e5 100644 --- a/src/Tomlyn/Syntax/IntegerValueSyntax.cs +++ b/src/Tomlyn/Syntax/IntegerValueSyntax.cs @@ -1,22 +1,47 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System.Globalization; + namespace Tomlyn.Syntax { + /// + /// An integer TOML value syntax node. + /// public sealed class IntegerValueSyntax : ValueSyntax { private SyntaxToken _token; + /// + /// Creates an + /// public IntegerValueSyntax() : base(SyntaxKind.Integer) { } + /// + /// Creates an + /// + /// The integer value + public IntegerValueSyntax(long value) : this() + { + Token = new SyntaxToken(TokenKind.Integer, value.ToString(CultureInfo.InvariantCulture)); + Value = value; + } + + /// + /// The integer token with its textual representation + /// public SyntaxToken Token { get => _token; set => ParentToThis(ref _token, value, value != null && value.TokenKind.IsInteger(), "decimal/hex/binary/octal integer"); } + /// + /// The parsed integer value + /// public long Value { get; set; } public override void Accept(SyntaxVisitor visitor) diff --git a/src/Tomlyn/Syntax/InvalidSyntaxToken.cs b/src/Tomlyn/Syntax/InvalidSyntaxToken.cs index c97cf57..1a95eb3 100644 --- a/src/Tomlyn/Syntax/InvalidSyntaxToken.cs +++ b/src/Tomlyn/Syntax/InvalidSyntaxToken.cs @@ -3,8 +3,14 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Syntax { + /// + /// Represents an invalid + /// public sealed class InvalidSyntaxToken : SyntaxToken { + /// + /// The kind of token which is invalid for the context. + /// public TokenKind InvalidKind { get; set; } } } \ No newline at end of file diff --git a/src/Tomlyn/Syntax/KeySyntax.cs b/src/Tomlyn/Syntax/KeySyntax.cs index f0729d0..1b6048c 100644 --- a/src/Tomlyn/Syntax/KeySyntax.cs +++ b/src/Tomlyn/Syntax/KeySyntax.cs @@ -1,24 +1,61 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System; + namespace Tomlyn.Syntax { + /// + /// A key TOML syntax node. + /// public sealed class KeySyntax : ValueSyntax { - private BasicValueSyntax _base; + private BareKeyOrStringValueSyntax _key; + /// + /// Creates a new instance of a + /// public KeySyntax() : base(SyntaxKind.Key) { - DotKeyItems = new SyntaxList() { Parent = this }; + DotKeys = new SyntaxList() { Parent = this }; + } + + /// + /// Creates a new instance of a + /// + /// A simple name of this key + public KeySyntax(string key) : this() + { + Key = BareKeySyntax.IsBareKey(key) ? (BareKeyOrStringValueSyntax)new BareKeySyntax(key) : new StringValueSyntax(key); + } + + /// + /// Creates a new instance of a + /// + /// the base key + /// the key after the dot + public KeySyntax(string key, string dotKey1) : this() + { + if (key == null) throw new ArgumentNullException(nameof(key)); + if (dotKey1 == null) throw new ArgumentNullException(nameof(dotKey1)); + Key = BareKeySyntax.IsBareKey(key) ? (BareKeyOrStringValueSyntax)new BareKeySyntax(key) : new StringValueSyntax(key); + DotKeys.Add(new DottedKeyItemSyntax(dotKey1)); } - public BasicValueSyntax Base + /// + /// The base of the key before the dot + /// + public BareKeyOrStringValueSyntax Key { - get => _base; - set => ParentToThis(ref _base, value); // TODO: add validation for type of key (basic key or string) + get => _key; + set => ParentToThis(ref _key, value); // TODO: add validation for type of key (basic key or string) } - public SyntaxList DotKeyItems { get; } + /// + /// List of the dotted keys. + /// + public SyntaxList DotKeys { get; } public override void Accept(SyntaxVisitor visitor) { @@ -29,8 +66,8 @@ public override void Accept(SyntaxVisitor visitor) protected override SyntaxNode GetChildrenImpl(int index) { - if (index == 0) return Base; - return DotKeyItems; + if (index == 0) return Key; + return DotKeys; } } } \ No newline at end of file diff --git a/src/Tomlyn/Syntax/KeyValueSyntax.cs b/src/Tomlyn/Syntax/KeyValueSyntax.cs index e1f5538..2605e0e 100644 --- a/src/Tomlyn/Syntax/KeyValueSyntax.cs +++ b/src/Tomlyn/Syntax/KeyValueSyntax.cs @@ -1,37 +1,90 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System; + namespace Tomlyn.Syntax { - public sealed class KeyValueSyntax : TableEntrySyntax + /// + /// A TOML key = value syntax node. + /// + public sealed class KeyValueSyntax : SyntaxNode { private KeySyntax _key; private SyntaxToken _equalToken; private ValueSyntax _value; private SyntaxToken _endOfLineToken; + /// + /// Creates an instance of + /// public KeyValueSyntax() : base(SyntaxKind.KeyValue) { } + /// + /// Creates an instance of + /// + /// The key + /// The value + public KeyValueSyntax(string key, ValueSyntax value) : this() + { + if (key == null) throw new ArgumentNullException(nameof(key)); + Key = new KeySyntax(key); + Value = value ?? throw new ArgumentNullException(nameof(value)); + EqualToken = SyntaxFactory.Token(TokenKind.Equal); + EqualToken.AddLeadingWhitespace(); + EqualToken.AddTrailingWhitespace(); + EndOfLineToken = SyntaxFactory.NewLine(); + } + + /// + /// Creates an instance of + /// + /// The key + /// The value + public KeyValueSyntax(KeySyntax key, ValueSyntax value) : this() + { + Key = key ?? throw new ArgumentNullException(nameof(key)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + EqualToken = SyntaxFactory.Token(TokenKind.Equal); + EqualToken.AddLeadingWhitespace(); + EqualToken.AddTrailingWhitespace(); + EndOfLineToken = SyntaxFactory.NewLine(); + } + + + /// + /// Gets or sets the key. + /// public KeySyntax Key { get => _key; set => ParentToThis(ref _key, value); } + /// + /// Gets or sets the `=` token + /// public SyntaxToken EqualToken { get => _equalToken; set => ParentToThis(ref _equalToken, value, TokenKind.Equal); } + /// + /// Gets or sets the value + /// public ValueSyntax Value { get => _value; set => ParentToThis(ref _value, value); } + /// + /// Gets or sets the new-line token. + /// public SyntaxToken EndOfLineToken { get => _endOfLineToken; diff --git a/src/Tomlyn/Syntax/SourceSpan.cs b/src/Tomlyn/Syntax/SourceSpan.cs index 4cd6d15..a513894 100644 --- a/src/Tomlyn/Syntax/SourceSpan.cs +++ b/src/Tomlyn/Syntax/SourceSpan.cs @@ -3,8 +3,17 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Syntax { + /// + /// A textual source span. + /// public struct SourceSpan { + /// + /// Creates a source span. + /// + /// + /// + /// public SourceSpan(string fileName, TextPosition start, TextPosition end) { FileName = fileName; @@ -12,15 +21,29 @@ public SourceSpan(string fileName, TextPosition start, TextPosition end) End = end; } + /// + /// Gets or sets the filename. + /// public string FileName; + /// + /// Gets the starting offset of this span. + /// public int Offset => Start.Offset; + /// + /// Gets the length of this span. + /// public int Length => End.Offset - Start.Offset + 1; - + /// + /// Gets or sets the starting text position. + /// public TextPosition Start; + /// + /// Gets or sets the ending text position. + /// public TextPosition End; public override string ToString() @@ -28,6 +51,9 @@ public override string ToString() return $"{FileName}{Start}-{End}"; } + /// + /// A string representation of this source span not including the position. + /// public string ToStringSimple() { return $"{FileName}{Start}"; diff --git a/src/Tomlyn/Syntax/StringValueSyntax.cs b/src/Tomlyn/Syntax/StringValueSyntax.cs index 9dc6483..b95de72 100644 --- a/src/Tomlyn/Syntax/StringValueSyntax.cs +++ b/src/Tomlyn/Syntax/StringValueSyntax.cs @@ -1,22 +1,49 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System; +using Tomlyn.Text; + namespace Tomlyn.Syntax { - public sealed class StringValueSyntax : BasicValueSyntax + /// + /// A string TOML syntax value node. + /// + public sealed class StringValueSyntax : BareKeyOrStringValueSyntax { private SyntaxToken _token; + /// + /// Creates a new instance of + /// public StringValueSyntax() : base(SyntaxKind.String) { } + /// + /// Creates a new instance of + /// + /// String value used for this node + public StringValueSyntax(string text) : this() + { + if (text == null) throw new ArgumentNullException(nameof(text)); + Token = new SyntaxToken(TokenKind.String, $"\"{text.EscapeForToml()}\""); + Value = text; + } + + /// + /// The token of the string. + /// public SyntaxToken Token { get => _token; set => ParentToThis(ref _token, value, value != null && value.TokenKind.IsString(), "string"); } + /// + /// The associated parsed string value + /// public string Value { get; set; } public override void Accept(SyntaxVisitor visitor) diff --git a/src/Tomlyn/Syntax/Syntax.cd b/src/Tomlyn/Syntax/Syntax.cd index 7e47b2d..e601d54 100644 --- a/src/Tomlyn/Syntax/Syntax.cd +++ b/src/Tomlyn/Syntax/Syntax.cd @@ -1,21 +1,14 @@  - + AIAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAIAAAA= Syntax\SyntaxNodeBase.cs - - - - - Syntax\SyntaxNode.cs - - - + AAAAAAAAAAFAEAAEAAAAAAAAAIAAQAAgAAACAgAAAAA= Syntax\SyntaxNode.cs @@ -26,20 +19,20 @@ - + AIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAA= Syntax\SyntaxTrivia.cs - - + + - - - - + + + + @@ -48,7 +41,7 @@ - + @@ -64,162 +57,173 @@ - + AMAAAAAAAAACAAAAAAAAAAAAAIAAQAAAAAAAAAAAAUA= Syntax\InlineTableItemSyntax.cs - + AIAAAAAAAAAAAAAAAAAAAAAAAIAAQAAAAAAACAAAAgA= Syntax\SyntaxToken.cs - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - Syntax\TableEntrySyntax.cs - - - + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= Syntax\ValueSyntax.cs - - + + + + + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - Syntax\BasicValueSyntax.cs + Syntax\BareKeyOrStringValueSyntax.cs - + AJAAAAAAAAAACAAAAAAAAAAAAIAAQAAAAAAgAAABAAA= Syntax\BooleanValueSyntax.cs - + AIAAAAIAAAAAAAAAAAAAAAAAAIAAQAAAAAAgAAABAAA= Syntax\DateTimeValueSyntax.cs - + - - - - + + + + - AJAAAAAAAAAAAAAAAAAIAAAACIAAQAAAAAAgAAAAAAA= + AIAAAAAAAAAAAAQAAACIAAAACIAAQAAAAAAAAAAAAAA= Syntax\DottedKeyItemSyntax.cs - - - - + AIAAAAAAAAAACAAAAAAAAAAAAIAAQAAAAAAgAAABAAA= Syntax\FloatValueSyntax.cs - - - - AoAAAAAAAAAACAAAAAIAAAgAAIAAQAAAAAAAQAAAAAA= - Syntax\InlineTableSyntax.cs - - - + AIAAAAAAAAAACAAAAAAAAAAAAIAAQAAAAAAgAAABAAA= Syntax\IntegerValueSyntax.cs - + - - - - + + + + + + - + - - - - + + + + - + + + + + + + + + + + - AIAAAAAAAAAAAAAAAAAAAAAAAIAAQAAAAABIAAAAQAA= + AIAAAAAAAAAAAAQAAACAAAAAAIAAQAAAEAAAAAAAAAA= Syntax\KeySyntax.cs - + - + - - + + - AIAAAAAAAAAAAAQAAACAAAAAAIAAQAAAAAAAAAAAAAA= - Syntax\BasicKeySyntax.cs + AIAAAAAAAAAACAQAAACAAAAAAIAAQAAAAAAAAAAAAAA= + Syntax\BareKeySyntax.cs - + AIAAAAAAAAAACAAAAAAAAAAAAIAAQAAAAAAgAAABAAA= Syntax\StringValueSyntax.cs - + AAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= Syntax\InvalidSyntaxToken.cs - - + + - - - - - - + + - - + + + + + + + + + + + + @@ -232,15 +236,17 @@ - - + + - - - - + + + - + + + + AgACIBAAAAAACAAAABAAAAQAAMABQAAAQAAAAAAAAIA= Syntax\TableSyntaxBase.cs @@ -250,14 +256,14 @@ - + AoAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAA= Syntax\TableArraySyntax.cs - + AoAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAA= Syntax\TableSyntax.cs @@ -271,12 +277,40 @@ - + - AAAAAAgAAAAAABAAAAAAAAAAAIAAQAAEAABBAAAAAAA= + AAIAAAAAAAAAABAAAAAAAAAAAIAAQAAEAABBAAAAAAA= Syntax\SyntaxList.cs + + + + + + + + + + + + AoAAAAAAAAAACAAAAAIAAAgAAIAAQAAAAAAAQAAAAAA= + Syntax\InlineTableSyntax.cs + + + + + + + + + AIAAABAAAAAACAAAABAAAAAAAIABQAAAQAAAAAAAAAA= + Syntax\ArraySyntax.cs + + + + + \ No newline at end of file diff --git a/src/Tomlyn/Syntax/SyntaxFactory.cs b/src/Tomlyn/Syntax/SyntaxFactory.cs new file mode 100644 index 0000000..0305211 --- /dev/null +++ b/src/Tomlyn/Syntax/SyntaxFactory.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2019 - Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System; +using System.Diagnostics; + +namespace Tomlyn.Syntax +{ + /// + /// A factory for + /// + public static class SyntaxFactory + { + /// + /// Creates a trivia whitespace. + /// + /// A trivia whitespace. + public static SyntaxTrivia Whitespace() + { + return new SyntaxTrivia(TokenKind.Whitespaces, " "); + } + + /// + /// Creates a newline trivia. + /// + /// A new line trivia + public static SyntaxTrivia NewLineTrivia() + { + return new SyntaxTrivia(TokenKind.NewLine, "\n"); + } + + /// + /// Creates a comment trivia. + /// + /// A comment trivia + /// A comment trivia + public static SyntaxTrivia Comment(string comment) + { + if (comment == null) throw new ArgumentNullException(nameof(comment)); + return new SyntaxTrivia(TokenKind.Comment, $"# {comment}"); + } + + /// + /// Creates a newline token. + /// + /// A new line token + public static SyntaxToken NewLine() + { + return new SyntaxToken(TokenKind.NewLine, "\n"); + } + + /// + /// Creates a token from the specified token kind. + /// + /// The token kind + /// The token + public static SyntaxToken Token(TokenKind kind) + { + if (kind == TokenKind.NewLine || !kind.IsToken()) throw new ArgumentOutOfRangeException($"The token kind `{kind}` is not supported for a plain token without a predefined value"); + var text = kind.ToText(); + Debug.Assert(text != null); + return new SyntaxToken(kind, text); + } + } +} \ No newline at end of file diff --git a/src/Tomlyn/Syntax/SyntaxList.cs b/src/Tomlyn/Syntax/SyntaxList.cs index b027498..2434d51 100644 --- a/src/Tomlyn/Syntax/SyntaxList.cs +++ b/src/Tomlyn/Syntax/SyntaxList.cs @@ -9,6 +9,9 @@ namespace Tomlyn.Syntax { + /// + /// Abstract list of + /// public abstract class SyntaxList : SyntaxNode { protected readonly List Children; @@ -31,13 +34,24 @@ public override void Accept(SyntaxVisitor visitor) } } + /// + /// Abstract list of + /// + /// Type of the node public sealed class SyntaxList : SyntaxList, IEnumerable where TSyntaxNode : SyntaxNode { + /// + /// Creates an instance of + /// public SyntaxList() { } - public void AddChildren(TSyntaxNode node) + /// + /// Adds the specified node to this list. + /// + /// Node to add to this list + public void Add(TSyntaxNode node) { if (node == null) throw new ArgumentNullException(nameof(node)); if (node.Parent != null) throw ThrowHelper.GetExpectingNoParentException(); @@ -55,6 +69,10 @@ protected override SyntaxNode GetChildrenImpl(int index) return Children[index]; } + /// + /// Removes a node at the specified index. + /// + /// Index of the node to remove public void RemoveChildrenAt(int index) { var node = Children[index]; @@ -62,6 +80,10 @@ public void RemoveChildrenAt(int index) node.Parent = null; } + /// + /// Removes the specified node instance. + /// + /// Node instance to remove public void RemoveChildren(TSyntaxNode node) { if (node == null) throw new ArgumentNullException(nameof(node)); @@ -70,6 +92,10 @@ public void RemoveChildren(TSyntaxNode node) node.Parent = null; } + /// + /// Gets the default enumerator. + /// + /// The enumerator of this list public Enumerator GetEnumerator() { return new Enumerator(Children); @@ -85,11 +111,18 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } + /// + /// Enumerator of a + /// public struct Enumerator : IEnumerator { private readonly List _nodes; private int _index; + /// + /// Initialize an enumerator with a list of + /// + /// public Enumerator(List nodes) { _nodes = nodes; diff --git a/src/Tomlyn/Syntax/SyntaxNode.cs b/src/Tomlyn/Syntax/SyntaxNode.cs index 1263e73..eebd7fe 100644 --- a/src/Tomlyn/Syntax/SyntaxNode.cs +++ b/src/Tomlyn/Syntax/SyntaxNode.cs @@ -8,7 +8,10 @@ using Tomlyn.Helpers; namespace Tomlyn.Syntax -{ +{ + /// + /// Base class used to define a TOML Syntax tree. + /// public abstract class SyntaxNode : SyntaxNodeBase { protected SyntaxNode(SyntaxKind kind) @@ -16,14 +19,31 @@ protected SyntaxNode(SyntaxKind kind) Kind = kind; } + /// + /// Gets the type of node. + /// public SyntaxKind Kind { get; } + /// + /// Gets the leading trivia attached to this node. Might be null if no leading trivias. + /// public List LeadingTrivia { get; set; } + /// + /// Gets the trailing trivia attached to this node. Might be null if no trailing trivias. + /// public List TrailingTrivia { get; set; } + /// + /// Gets the number of children + /// public abstract int ChildrenCount { get; } + /// + /// Gets a children at the specified index. + /// + /// Index of the children + /// A children at the specified index public SyntaxNode GetChildren(int index) { if (index < 0) throw ThrowHelper.GetIndexNegativeArgumentOutOfRangeException(); @@ -31,6 +51,12 @@ public SyntaxNode GetChildren(int index) return GetChildrenImpl(index); } + /// + /// Gets a children at the specified index. + /// + /// Index of the children + /// A children at the specified index + /// The index is safe to use protected abstract SyntaxNode GetChildrenImpl(int index); public override string ToString() @@ -40,6 +66,10 @@ public override string ToString() return writer.ToString(); } + /// + /// Writes this node to a textual TOML representation + /// + /// A writer to receive the TOML output public void WriteTo(TextWriter writer) { if (writer == null) throw new ArgumentNullException(nameof(writer)); @@ -83,6 +113,12 @@ private static void WriteTriviaTo(List trivias, TextWriter writer) } } + /// + /// Helper method to deparent/parent a node to this instance. + /// + /// Type of the node + /// The previous child node parented to this instance + /// The new child node to parent to this instance protected void ParentToThis(ref TSyntaxNode set, TSyntaxNode node) where TSyntaxNode : SyntaxNode { if (node?.Parent != null) throw ThrowHelper.GetExpectingNoParentException(); @@ -97,17 +133,41 @@ protected void ParentToThis(ref TSyntaxNode set, TSyntaxNode node) set = node; } + /// + /// Helper method to deparent/parent a to this instance with an expected kind of token. + /// + /// Type of the node + /// The previous child node parented to this instance + /// The new child node to parent to this instance + /// The expected kind of token protected void ParentToThis(ref TSyntaxNode set, TSyntaxNode node, TokenKind expectedKind) where TSyntaxNode : SyntaxToken { ParentToThis(ref set, node, node.TokenKind == expectedKind, expectedKind); } + /// + /// Helper method to deparent/parent a to this instance with an expected kind of token condition. + /// + /// Type of the node + /// The type of message + /// The previous child node parented to this instance + /// The new child node to parent to this instance + /// true if kind is matching, false otherwise + /// The message to display if the kind is not matching protected void ParentToThis(ref TSyntaxNode set, TSyntaxNode node, bool expectedKindSuccess, TExpected expectedMessage) where TSyntaxNode : SyntaxToken { if (node != null && !expectedKindSuccess) throw new InvalidOperationException($"Unexpected node kind `{node.TokenKind}` while expecting `{expectedMessage}`"); ParentToThis(ref set, node); } + /// + /// Helper method to deparent/parent a to this instance with an expected kind of token. + /// + /// Type of the node + /// The previous child node parented to this instance + /// The new child node to parent to this instance + /// The expected kind of token (option1) + /// The expected kind of token (option2) protected void ParentToThis(ref TSyntaxNode set, TSyntaxNode node, TokenKind expectedKind1, TokenKind expectedKind2) where TSyntaxNode : SyntaxToken { ParentToThis(ref set, node, node.TokenKind == expectedKind1 || node.TokenKind == expectedKind2, new ExpectedTuple2(expectedKind1, expectedKind2)); diff --git a/src/Tomlyn/Syntax/SyntaxNodeBase.cs b/src/Tomlyn/Syntax/SyntaxNodeBase.cs index e8d7467..c304ec0 100644 --- a/src/Tomlyn/Syntax/SyntaxNodeBase.cs +++ b/src/Tomlyn/Syntax/SyntaxNodeBase.cs @@ -3,12 +3,25 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Syntax { + /// + /// Base class for and + /// public abstract class SyntaxNodeBase { + /// + /// The text source span, read-write, manually updated from children. + /// public SourceSpan Span; + /// + /// Allow to visit this instance with the specified visitor. + /// + /// The visitor public abstract void Accept(SyntaxVisitor visitor); + /// + /// Gets the parent of this node. + /// public SyntaxNode Parent { get; internal set; } } } \ No newline at end of file diff --git a/src/Tomlyn/Syntax/SyntaxNodeExtensions.cs b/src/Tomlyn/Syntax/SyntaxNodeExtensions.cs new file mode 100644 index 0000000..785d5e2 --- /dev/null +++ b/src/Tomlyn/Syntax/SyntaxNodeExtensions.cs @@ -0,0 +1,100 @@ +// Copyright (c) 2019 - Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System; +using System.Collections.Generic; + +namespace Tomlyn.Syntax +{ + public static class SyntaxNodeExtensions + { + public static void Add(this SyntaxList list, string name, int value) + { + if (list == null) throw new ArgumentNullException(nameof(list)); + list.Add(new KeyValueSyntax(name, new IntegerValueSyntax(value))); + } + + public static void Add(this SyntaxList list, string name, long value) + { + if (list == null) throw new ArgumentNullException(nameof(list)); + list.Add(new KeyValueSyntax(name, new IntegerValueSyntax(value))); + } + + public static void Add(this SyntaxList list, string name, bool value) + { + if (list == null) throw new ArgumentNullException(nameof(list)); + list.Add(new KeyValueSyntax(name, new BooleanValueSyntax(value))); + } + + public static void Add(this SyntaxList list, string name, double value) + { + if (list == null) throw new ArgumentNullException(nameof(list)); + list.Add(new KeyValueSyntax(name, new FloatValueSyntax(value))); + } + + public static void Add(this SyntaxList list, string name, string value) + { + if (list == null) throw new ArgumentNullException(nameof(list)); + list.Add(new KeyValueSyntax(name, new StringValueSyntax(value))); + } + + public static void Add(this SyntaxList list, string name, int[] values) + { + if (list == null) throw new ArgumentNullException(nameof(list)); + list.Add(new KeyValueSyntax(name, new ArraySyntax(values))); + } + + public static void Add(this SyntaxList list, string name, string[] values) + { + if (list == null) throw new ArgumentNullException(nameof(list)); + list.Add(new KeyValueSyntax(name, new ArraySyntax(values))); + } + + public static KeyValueSyntax AddComment(this KeyValueSyntax keyValue, string comment) + { + if (keyValue == null) throw new ArgumentNullException(nameof(keyValue)); + if (keyValue.Value == null) throw new InvalidOperationException("The Value must not be null on the KeyValueSyntax"); + keyValue.Value.AddTrailingWhitespace().AddComment(comment); + return keyValue; + } + + public static T AddLeadingWhitespace(this T node) where T : SyntaxNode + { + if (node == null) throw new ArgumentNullException(nameof(node)); + var trivias = node.LeadingTrivia; + if (trivias == null) + { + trivias = new List(); + node.LeadingTrivia = trivias; + } + trivias.Add(SyntaxFactory.Whitespace()); + return node; + } + + public static T AddTrailingWhitespace(this T node) where T : SyntaxNode + { + if (node == null) throw new ArgumentNullException(nameof(node)); + var trivias = node.TrailingTrivia; + if (trivias == null) + { + trivias = new List(); + node.TrailingTrivia = trivias; + } + trivias.Add(SyntaxFactory.Whitespace()); + return node; + } + + public static T AddComment(this T node, string comment) where T : SyntaxNode + { + var trivias = node.TrailingTrivia; + if (trivias == null) + { + trivias = new List(); + node.TrailingTrivia = trivias; + } + trivias.Add(SyntaxFactory.Comment(comment)); + return node; + } + } +} \ No newline at end of file diff --git a/src/Tomlyn/Syntax/SyntaxToken.cs b/src/Tomlyn/Syntax/SyntaxToken.cs index c30530b..9bbe18b 100644 --- a/src/Tomlyn/Syntax/SyntaxToken.cs +++ b/src/Tomlyn/Syntax/SyntaxToken.cs @@ -3,14 +3,37 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Syntax { + /// + /// A token node. + /// public class SyntaxToken : SyntaxNode { - public TokenKind TokenKind { get; set; } - + /// + /// Creates a new instance of + /// public SyntaxToken() : base(SyntaxKind.Token) { } + /// + /// Creates a new instance of + /// + /// The type of token + /// The associated textual representation + public SyntaxToken(TokenKind tokenKind, string text) : this() + { + TokenKind = tokenKind; + Text = text; + } + + /// + /// Gets or sets the kind of token. + /// + public TokenKind TokenKind { get; set; } + + /// + /// Gets or sets the associated text + /// public string Text { get; set; } public override void Accept(SyntaxVisitor visitor) diff --git a/src/Tomlyn/Syntax/SyntaxTrivia.cs b/src/Tomlyn/Syntax/SyntaxTrivia.cs index 6576d7e..f9e952d 100644 --- a/src/Tomlyn/Syntax/SyntaxTrivia.cs +++ b/src/Tomlyn/Syntax/SyntaxTrivia.cs @@ -1,10 +1,24 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System; + namespace Tomlyn.Syntax { public sealed class SyntaxTrivia : SyntaxNodeBase { + public SyntaxTrivia() + { + } + + public SyntaxTrivia(TokenKind kind, string text) + { + if (!kind.IsTrivia()) throw new ArgumentOutOfRangeException(nameof(kind), $"The kind `{kind}` is not a trivia"); + Kind = kind; + Text = text ?? throw new ArgumentNullException(nameof(text)); + } + public TokenKind Kind { get; set; } public string Text { get; set; } diff --git a/src/Tomlyn/Syntax/SyntaxValidator.cs b/src/Tomlyn/Syntax/SyntaxValidator.cs index d146607..6803c00 100644 --- a/src/Tomlyn/Syntax/SyntaxValidator.cs +++ b/src/Tomlyn/Syntax/SyntaxValidator.cs @@ -104,14 +104,14 @@ public override void Visit(TableArraySyntax table) private void KeyNameToObjectPath(KeySyntax key, ObjectKind kind) { - var name = GetStringFromBasic(key.Base); + var name = GetStringFromBasic(key.Key); _currentPath.Add(name); - var items = key.DotKeyItems; + var items = key.DotKeys; for (int i = 0; i < items.ChildrenCount; i++) { AddObjectPath(key, kind, true); - var dotItem = GetStringFromBasic(items.GetChildren(i).Value); + var dotItem = GetStringFromBasic(items.GetChildren(i).Key); _currentPath.Add(dotItem); } } @@ -139,9 +139,9 @@ private ObjectPathValue AddObjectPath(SyntaxNode node, ObjectKind kind, bool isI return existingValue; } - private string GetStringFromBasic(BasicValueSyntax value) + private string GetStringFromBasic(BareKeyOrStringValueSyntax value) { - if (value is BasicKeySyntax basicKey) + if (value is BareKeySyntax basicKey) { return basicKey.Key.Text; } diff --git a/src/Tomlyn/Syntax/SyntaxVisitor.cs b/src/Tomlyn/Syntax/SyntaxVisitor.cs index d76453a..cd93119 100644 --- a/src/Tomlyn/Syntax/SyntaxVisitor.cs +++ b/src/Tomlyn/Syntax/SyntaxVisitor.cs @@ -53,9 +53,9 @@ public virtual void Visit(SyntaxToken token) public virtual void Visit(SyntaxTrivia trivia) { } - public virtual void Visit(BasicKeySyntax basicKey) + public virtual void Visit(BareKeySyntax bareKey) { - DefaultVisit(basicKey); + DefaultVisit(bareKey); } public virtual void Visit(KeySyntax key) { diff --git a/src/Tomlyn/Syntax/TableArraySyntax.cs b/src/Tomlyn/Syntax/TableArraySyntax.cs index 6a29b63..3e11ee1 100644 --- a/src/Tomlyn/Syntax/TableArraySyntax.cs +++ b/src/Tomlyn/Syntax/TableArraySyntax.cs @@ -1,3 +1,5 @@ +using System; + namespace Tomlyn.Syntax { public sealed class TableArraySyntax : TableSyntaxBase @@ -6,6 +8,23 @@ public TableArraySyntax() : base(SyntaxKind.TableArray) { } + public TableArraySyntax(string name) : this() + { + if (name == null) throw new ArgumentNullException(nameof(name)); + Name = new KeySyntax(name); + OpenBracket = SyntaxFactory.Token(TokenKind.OpenBracketDouble); + CloseBracket = SyntaxFactory.Token(TokenKind.CloseBracketDouble); + EndOfLineToken = SyntaxFactory.NewLine(); + } + + public TableArraySyntax(KeySyntax name) : this() + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + OpenBracket = SyntaxFactory.Token(TokenKind.OpenBracketDouble); + CloseBracket = SyntaxFactory.Token(TokenKind.CloseBracketDouble); + EndOfLineToken = SyntaxFactory.NewLine(); + } + public override void Accept(SyntaxVisitor visitor) { visitor.Visit(this); diff --git a/src/Tomlyn/Syntax/TableEntrySyntax.cs b/src/Tomlyn/Syntax/TableEntrySyntax.cs deleted file mode 100644 index a5cec6e..0000000 --- a/src/Tomlyn/Syntax/TableEntrySyntax.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2019 - Alexandre Mutel. All rights reserved. -// Licensed under the BSD-Clause 2 license. -// See license.txt file in the project root for full license information. -namespace Tomlyn.Syntax -{ - public abstract class TableEntrySyntax : SyntaxNode - { - protected TableEntrySyntax(SyntaxKind kind) : base(kind) - { - } - } -} \ No newline at end of file diff --git a/src/Tomlyn/Syntax/TableSyntax.cs b/src/Tomlyn/Syntax/TableSyntax.cs index 48a0e9c..39e50f5 100644 --- a/src/Tomlyn/Syntax/TableSyntax.cs +++ b/src/Tomlyn/Syntax/TableSyntax.cs @@ -1,6 +1,9 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + +using System; + namespace Tomlyn.Syntax { public sealed class TableSyntax : TableSyntaxBase @@ -9,6 +12,23 @@ public TableSyntax() : base(SyntaxKind.Table) { } + public TableSyntax(string name) : this() + { + if (name == null) throw new ArgumentNullException(nameof(name)); + OpenBracket = SyntaxFactory.Token(TokenKind.OpenBracket); + Name = new KeySyntax(name); + CloseBracket = SyntaxFactory.Token(TokenKind.CloseBracket); + EndOfLineToken = SyntaxFactory.NewLine(); + } + + public TableSyntax(KeySyntax name) : this() + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + OpenBracket = SyntaxFactory.Token(TokenKind.OpenBracket); + CloseBracket = SyntaxFactory.Token(TokenKind.CloseBracket); + EndOfLineToken = SyntaxFactory.NewLine(); + } + public override void Accept(SyntaxVisitor visitor) { visitor.Visit(this); diff --git a/src/Tomlyn/Syntax/TableSyntaxBase.cs b/src/Tomlyn/Syntax/TableSyntaxBase.cs index 6b6806f..382e17b 100644 --- a/src/Tomlyn/Syntax/TableSyntaxBase.cs +++ b/src/Tomlyn/Syntax/TableSyntaxBase.cs @@ -1,6 +1,9 @@ namespace Tomlyn.Syntax { - public abstract class TableSyntaxBase : TableEntrySyntax + /// + /// Base class for a or a + /// + public abstract class TableSyntaxBase : SyntaxNode { private SyntaxToken _openBracket; private KeySyntax _name; @@ -12,31 +15,47 @@ internal TableSyntaxBase(SyntaxKind kind) : base(kind) Items = new SyntaxList() { Parent = this }; } + /// + /// Gets or sets the open bracket (simple `[` for , double `[[` for ) + /// public SyntaxToken OpenBracket { get => _openBracket; set => ParentToThis(ref _openBracket, value, OpenTokenKind); } + /// + /// Gets or sets the name of this table + /// public KeySyntax Name { get => _name; set => ParentToThis(ref _name, value); } + /// + /// Gets or sets the close bracket (simple `]` for , double `]]` for ) + /// public SyntaxToken CloseBracket { get => _closeBracket; set => ParentToThis(ref _closeBracket, value, CloseTokenKind); } + /// + /// Gets the new-line. + /// public SyntaxToken EndOfLineToken { get => _endOfLineToken; set => ParentToThis(ref _endOfLineToken, value, TokenKind.NewLine, TokenKind.Eof); } + /// + /// Gets the key-values associated with this table. + /// public SyntaxList Items { get; } + public override int ChildrenCount => 5; internal abstract TokenKind OpenTokenKind { get; } diff --git a/src/Tomlyn/Syntax/TextPosition.cs b/src/Tomlyn/Syntax/TextPosition.cs index f76fd74..b85b4d3 100644 --- a/src/Tomlyn/Syntax/TextPosition.cs +++ b/src/Tomlyn/Syntax/TextPosition.cs @@ -6,10 +6,19 @@ namespace Tomlyn.Syntax { + /// + /// A position within a text (offset, line column) + /// public struct TextPosition : IEquatable { public static readonly TextPosition Eof = new TextPosition(-1, -1, -1); + /// + /// Creates a new instance of a + /// + /// Offset in the source text + /// Line number - zero based + /// Column number - zero based public TextPosition(int offset, int line, int column) { Offset = offset; @@ -17,22 +26,21 @@ public TextPosition(int offset, int line, int column) Line = line; } + /// + /// Gets or sets the offset. + /// public int Offset { get; set; } + /// + /// Gets or sets the column number (zero based) + /// public int Column { get; set; } + /// + /// Gets or sets the line number (zero based) + /// public int Line { get; set; } - public TextPosition NextColumn(int offset = 1) - { - return new TextPosition(Offset + offset, Line, Column + offset); - } - - public TextPosition NextLine(int offset = 1) - { - return new TextPosition(Offset + offset, Line + offset, 0); - } - public override string ToString() { return $"({Line+1},{Column+1})"; diff --git a/src/Tomlyn/Syntax/TokenKindExtensions.cs b/src/Tomlyn/Syntax/TokenKindExtensions.cs index 08c68a2..da0e2f1 100644 --- a/src/Tomlyn/Syntax/TokenKindExtensions.cs +++ b/src/Tomlyn/Syntax/TokenKindExtensions.cs @@ -1,10 +1,19 @@ // Copyright (c) 2019 - Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. + namespace Tomlyn.Syntax { + /// + /// Helper functions for + /// public static class TokenKindExtensions { + /// + /// Gets a textual representation of a token kind or null if not applicable (e.g TokenKind.Integer) + /// + /// A token kind + /// A textual representation of a token kind or null if not applicable (e.g TokenKind.Integer) public static string ToText(this TokenKind kind) { switch (kind) @@ -48,6 +57,11 @@ public static string ToText(this TokenKind kind) return null; } + /// + /// Checks if the specified kind is a float. + /// + /// A token kind + /// true if the specified kind is a float. public static bool IsFloat(this TokenKind kind) { switch (kind) @@ -65,6 +79,11 @@ public static bool IsFloat(this TokenKind kind) return false; } + /// + /// Checks if the specified kind is an integer + /// + /// A token kind + /// true if the specified kind is an integer. public static bool IsInteger(this TokenKind kind) { switch (kind) @@ -79,6 +98,30 @@ public static bool IsInteger(this TokenKind kind) return false; } + /// + /// Checks if the specified kind is a datetime. + /// + /// A token kind + /// true if the specified kind is a datetime. + public static bool IsDateTime(this TokenKind kind) + { + switch (kind) + { + case TokenKind.OffsetDateTime: + case TokenKind.LocalDateTime: + case TokenKind.LocalDate: + case TokenKind.LocalTime: + return true; + } + + return false; + } + + /// + /// Checks if the specified kind is a string. + /// + /// A token kind + /// true if the specified kind is a string public static bool IsString(this TokenKind kind) { switch (kind) @@ -93,5 +136,55 @@ public static bool IsString(this TokenKind kind) return false; } + /// + /// Checks if the specified kind is a trivia. + /// + /// A token kind + /// true if the specified kind is a trivia + public static bool IsTrivia(this TokenKind kind) + { + switch (kind) + { + case TokenKind.Whitespaces: + case TokenKind.NewLine: + case TokenKind.Comment: + return true; + default: + return false; + } + } + + /// + /// Checks if the specified kind is a token for which will return not null + /// + /// A token kind + /// true if the specified kind is a simple token + public static bool IsToken(this TokenKind kind) + { + switch (kind) + { + case TokenKind.NewLine: + case TokenKind.Comma: + case TokenKind.Dot: + case TokenKind.Equal: + case TokenKind.OpenBracket: + case TokenKind.OpenBracketDouble: + case TokenKind.CloseBracket: + case TokenKind.CloseBracketDouble: + case TokenKind.OpenBrace: + case TokenKind.CloseBrace: + case TokenKind.True: + case TokenKind.False: + case TokenKind.Infinite: + case TokenKind.PositiveInfinite: + case TokenKind.NegativeInfinite: + case TokenKind.Nan: + case TokenKind.PositiveNan: + case TokenKind.NegativeNan: + return true; + default: + return false; + } + } } } \ No newline at end of file diff --git a/src/Tomlyn/Syntax/ValueSyntax.cs b/src/Tomlyn/Syntax/ValueSyntax.cs index b54ff8b..ea8f27d 100644 --- a/src/Tomlyn/Syntax/ValueSyntax.cs +++ b/src/Tomlyn/Syntax/ValueSyntax.cs @@ -3,9 +3,12 @@ // See license.txt file in the project root for full license information. namespace Tomlyn.Syntax { + /// + /// Base class for all TOML values. + /// public abstract class ValueSyntax : SyntaxNode { - protected ValueSyntax(SyntaxKind kind) : base(kind) + internal ValueSyntax(SyntaxKind kind) : base(kind) { } } diff --git a/src/Tomlyn/Text/CharHelper.cs b/src/Tomlyn/Text/CharHelper.cs index c6676c8..2704af1 100644 --- a/src/Tomlyn/Text/CharHelper.cs +++ b/src/Tomlyn/Text/CharHelper.cs @@ -57,7 +57,61 @@ public static bool IsDateTime(char32 c) { return IsDigit(c) || c == ':' || c == '-' || c == 'Z' || c == 'T' || c == 'z' || c == 't' || c == '+' || c == '.'; } - + + /// + /// Escape a C# string to a TOML string + /// + public static string EscapeForToml(this string text) + { + StringBuilder builder = null; + for (int i = 0; i < text.Length; i++) + { + var c = text[i]; + var str = EscapeChar(text[i]); + if (str != null) + { + if (builder == null) + { + builder = new StringBuilder(text.Length * 2); + builder.Append(text.Substring(0, i)); + } + builder.Append(str); + } + else + { + builder?.Append(c); + } + } + return builder?.ToString() ?? text; + } + + private static string EscapeChar(char c) + { + if (c < ' ' || c == '"' || c == '\\') + { + switch (c) + { + case '\b': + return @"\b"; + case '\t': + return @"\t"; + case '\n': + return @"\n"; + case '\f': + return @"\f"; + case '\r': + return @"\r"; + case '"': + return @"\"""; + case '\\': + return @"\\"; + default: + return $"\\u{(int)c:X};"; + } + } + return null; + } + /// /// Converts a string that may have control characters to a printable string /// diff --git a/src/Tomlyn/Toml.cs b/src/Tomlyn/Toml.cs index 549b544..fa493f7 100644 --- a/src/Tomlyn/Toml.cs +++ b/src/Tomlyn/Toml.cs @@ -6,8 +6,18 @@ namespace Tomlyn { + /// + /// Main entry class to parse, validate and transform to a model a TOML document. + /// public static class Toml { + /// + /// Parses a text to a TOML document. + /// + /// A string representing a TOML document + /// An optional path/file name to identify errors + /// Options for parsing. Default is parse and validate. + /// A parsed TOML document public static DocumentSyntax Parse(string text, string sourcePath = null, TomlParserOptions options = TomlParserOptions.ParseAndValidate) { var textView = new StringSourceView(text, sourcePath ?? string.Empty); @@ -21,6 +31,13 @@ public static DocumentSyntax Parse(string text, string sourcePath = null, TomlPa return doc; } + /// + /// Parses a UTF8 byte array to a TOML document. + /// + /// A UTF8 string representing a TOML document + /// An optional path/file name to identify errors + /// Options for parsing. Default is parse and validate. + /// A parsed TOML document public static DocumentSyntax Parse(byte[] utf8Bytes, string sourcePath = null, TomlParserOptions options = TomlParserOptions.ParseAndValidate) { var textView = new StringUtf8SourceView(utf8Bytes, sourcePath ?? string.Empty); @@ -34,12 +51,22 @@ public static DocumentSyntax Parse(byte[] utf8Bytes, string sourcePath = null, T return doc; } + /// + /// Converts a to a + /// + /// A TOML document + /// A , a runtime representation of the TOML document public static TomlTable ToModel(this DocumentSyntax syntax) { if (syntax == null) throw new ArgumentNullException(nameof(syntax)); return TomlTable.From(syntax); } + /// + /// Validates the specified TOML document. + /// + /// The TOML document to validate + /// The same instance as the parameter. Check and for details. public static DocumentSyntax Validate(DocumentSyntax doc) { if (doc == null) throw new ArgumentNullException(nameof(doc)); diff --git a/src/Tomlyn/TomlParserOptions.cs b/src/Tomlyn/TomlParserOptions.cs index dcf359c..7dfb227 100644 --- a/src/Tomlyn/TomlParserOptions.cs +++ b/src/Tomlyn/TomlParserOptions.cs @@ -3,10 +3,19 @@ // See license.txt file in the project root for full license information. namespace Tomlyn { + /// + /// Options for parsing a TOML string. + /// public enum TomlParserOptions { + /// + /// Parse and validate. + /// ParseAndValidate = 0, + /// + /// Parse only the document. + /// ParseOnly = 1, } } \ No newline at end of file