public void NumberListColumn_Basics() { NumberListColumn <int> column = new NumberListColumn <int>(); column[0].SetTo(new ArraySlice <int>(new int[] { 0, 1, 2 })); TreeDiagnostics diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); int tinyColumnLength = (int)diagnostics.Length; Column.Basics(() => new NumberListColumn <int>(), NumberList <int> .Empty, column[0], (index) => { column[index].SetTo(new ArraySlice <int>(new int[] { index, index + 1, index + 2 })); return(column[index]); }); // NumberList Equals, GetHashCode int[] asArray = new int[] { 0, 1, 3 }; column[1].SetTo(new ArraySlice <int>(asArray)); Assert.False(column[0] == column[1]); Assert.True(column[0] != column[1]); Assert.NotEqual(column[0].GetHashCode(), column[1].GetHashCode()); // Compare to array Assert.True(column[1].Equals(asArray)); }
public void NumberListColumn_NullableCases() { // StringColumns are extremely common in object models, // so having very compact representations for common cases // is really important to file size for small databases. GenericNumberListColumn <int> column = new GenericNumberListColumn <int>(Nullability.DefaultToNull); TreeDiagnostics diagnostics; // Empty: { } diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(diagnostics.Length <= 2); // All null: { IsNull: { Count: 100, Capacity: 100 } } for (int i = 0; i < 100; ++i) { column[i] = null; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 13); // All empty: Only nulls false written List <int> empty = new List <int>(); for (int i = 0; i < 100; ++i) { column[i] = empty; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 13); // No nulls, No Empty: 4b + 2.125b / value (613b) + 4 pages x 4b (16b) + overhead (~10b) List <int> single = new List <int>(); single.Add(1); for (int i = 0; i < 100; ++i) { column[i] = single; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 640); // Nulls and Non-Nulls; both parts must be written column[50] = null; CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(2 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 670); }
public void NumberListColumn_NumberList_Basics() { // Set up column with sample values, roundtrip, re-verify NumberListColumn <int> column = BuildSampleColumn(); // Verify second value is in a shared array, not at index zero, not expandable (yet), not ReadOnly NumberList <int> slice = column[1]; Assert.Equal(4, slice.Count); Assert.True(slice.Slice.Index > 0); Assert.False(slice.Slice.IsExpandable); // Test second sample row slice IList members CollectionChangeVerifier.VerifyList(slice, (index) => index % 20); // Verify expandable after test Assert.Equal(0, slice.Slice.Index); Assert.True(slice.Slice.IsExpandable); // Verify values are re-merged and re-loaded properly string values = string.Join(", ", slice); column = TreeSerializer.RoundTrip(column, TreeFormat.Binary); Assert.Equal(values, string.Join(", ", column[1])); // Column range check Assert.Throws <IndexOutOfRangeException>(() => column[-1]); }
private NumberListColumn <int> BuildSampleColumn() { List <ArraySlice <int> > expected = new List <ArraySlice <int> >(); expected.Add(new ArraySlice <int>(new int[] { 5, 6, 7 })); expected.Add(new ArraySlice <int>(new int[] { 2, 3, 4, 5 })); expected.Add(new ArraySlice <int>(Enumerable.Range(0, 8192).ToArray())); NumberListColumn <int> column = new NumberListColumn <int>(); NumberListColumn <int> roundTripped; // Set each list, and verify they are correct when read back for (int i = 0; i < expected.Count; ++i) { column[i].SetTo(expected[i]); } for (int i = 0; i < expected.Count; ++i) { CollectionReadVerifier.VerifySame(expected[i], column[i]); } // Round trip and verify they deserialize correctly (note, these will be in a shared array now) roundTripped = TreeSerializer.RoundTrip(column, TreeFormat.Binary); for (int i = 0; i < expected.Count; ++i) { CollectionReadVerifier.VerifySame(expected[i], column[i]); } return(roundTripped); }
internal static void VerifyRoundTrip <T>(ArraySlice <T> slice, T[] copyToTargetArray) where T : unmanaged, IEquatable <T> { ArraySlice <T> roundTripped = TreeSerializer.RoundTrip(slice, TreeFormat.Binary); CollectionReadVerifier.VerifySame <T>(slice, roundTripped); VerifyCopyTo <T>(roundTripped, copyToTargetArray); }
public void FirstTileSetJsonTests() { // arrange var zUpBoxes = BBTestDataReader.GetTestData("testfixtures/zupboxes_actual.txt"); var tree = TileCutter.ConstructTree(zUpBoxes, 50, 2000.0); var translation = new double[] { 141584.2745, 471164.637, 15.81555842685751 }; var s = File.ReadAllText(@"./testfixtures/tileset_json_expected.json"); var tileset_json_expected = JsonConvert.DeserializeObject <TileSet>(s); // act var tileset_json_actual = TreeSerializer.ToTileset(tree, translation); var actual_json = JsonConvert.SerializeObject(tileset_json_actual, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); //File.WriteAllText("d:/aaa/sample_tileset_actual.json", actual_json); // assert Assert.IsTrue(tileset_json_actual.asset.version == "1.0"); Assert.IsTrue(tileset_json_actual.geometricError == 500); var all_children_actual = GetAllChildren(tileset_json_actual.root); var all_children_expected = GetAllChildren(tileset_json_expected.root); var res = AreSimilar(all_children_actual, all_children_expected); var sim = IsSimilar(tileset_json_expected.root, tileset_json_actual.root); Assert.IsTrue(sim); Assert.IsTrue(tileset_json_actual.root.refine == "ADD"); // 500 Assert.IsTrue(tileset_json_actual.root.geometricError == 500); // 500 Assert.IsTrue(tileset_json_actual.root.transform.Length == 16); // 500 Assert.IsTrue(tileset_json_actual.root.boundingVolume.box.Length == 12); Assert.IsTrue(tileset_json_actual.root.children.Count == 1); }
public static void Load(this ITreeSerializable item, Stream stream, TreeFormat format, TreeSerializationSettings settings = null) { using (ITreeReader reader = TreeSerializer.Reader(format, stream, settings)) { item.Read(reader); } }
public static void Save(this ITreeSerializable item, Stream stream, TreeFormat format, TreeSerializationSettings settings = null) { using (ITreeWriter writer = TreeSerializer.Writer(format, stream, settings)) { item.Write(writer); } }
public void SerializerTreeVisitorVisitStringLiteralTest() { var serializer = new JsonNodeWriter(); TreeSerializer visitor = new TreeSerializer(serializer); visitor.VisitStringLiteral(new StringLiteral(new Span(0, 10), "hello world")); Assert.AreEqual("\"StringLiteral\" : {\n \"Span\" : {\n \"start\" : \"0\",\n \"end\" : \"10\"\n },\n \"Value\" : \"hello world\"\n}", serializer.ToString()); }
public void ArraySliceTests_Basics() { int[] sample = Enumerable.Range(100, 50).ToArray(); int[] copyToTarget = new int[100]; ArraySlice <int> slice; // Empty ArraySlice slice = ArraySlice <int> .Empty; slice.Trim(); Assert.Empty(slice); Assert.True(slice == ArraySlice <int> .Empty); Assert.False(slice != ArraySlice <int> .Empty); VerifyCopyTo <int>(slice, copyToTarget); VerifyRoundTrip <int>(slice, copyToTarget); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Json)); // Whole Array slice = new ArraySlice <int>(sample); Assert.Equal(sample.Length, slice.Count); Assert.Equal(sample, slice); Assert.Equal(sample[10], slice[10]); VerifyCopyTo <int>(slice, copyToTarget); VerifyRoundTrip <int>(slice, copyToTarget); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Json)); // Array slice-to-end slice = new ArraySlice <int>(sample, index: 10); Assert.Equal(sample.Length - 10, slice.Count); Assert.Equal(sample[20], slice[10]); Assert.False(slice.Equals(sample)); VerifyCopyTo <int>(slice, copyToTarget); VerifyRoundTrip <int>(slice, copyToTarget); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Json)); // Array slice slice = new ArraySlice <int>(sample, index: 10, length: 20); Assert.Equal(20, slice.Count); Assert.Equal(sample[10], slice[0]); VerifyCopyTo <int>(slice, copyToTarget); VerifyRoundTrip <int>(slice, copyToTarget); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(slice, TreeSerializer.RoundTrip(slice, TreeFormat.Json)); // Bounds checks Assert.Throws <ArgumentNullException>(() => new ArraySlice <int>(null, 0, 0)); // Array null Assert.Throws <ArgumentOutOfRangeException>(() => new ArraySlice <int>(sample, -1, 0)); // index < 0 Assert.Throws <ArgumentOutOfRangeException>(() => new ArraySlice <int>(sample, sample.Length + 1, 0)); // index > array.Length Assert.Throws <ArgumentOutOfRangeException>(() => new ArraySlice <int>(sample, 0, sample.Length + 1)); // length too long Assert.Throws <ArgumentOutOfRangeException>(() => new ArraySlice <int>(sample, 2, sample.Length + 3)); // Clear slice.Clear(); Assert.Empty(slice); }
public void RefColumn_Basics() { string referencedTable = "ReferencedTable"; Column.Basics <int>(() => new RefColumn(referencedTable), -1, 10, (index) => 2 * index); // Verify ReferencedTableName stored and correctly kept after deserialize Assert.Equal(referencedTable, TreeSerializer.RoundTrip(new RefColumn(referencedTable), () => new RefColumn(referencedTable), TreeFormat.Binary).ReferencedTableName); }
public void RefListColumn_Basics() { string referencedTable = "ReferencedTable"; RefListColumn column = new RefListColumn(referencedTable); RefListColumn roundTripped = TreeSerializer.RoundTrip(column, () => new RefListColumn(referencedTable), TreeFormat.Binary); Assert.Equal(referencedTable, roundTripped.ReferencedTableName); }
private static void WiteTilesetJson(double[] translation, Node tree, string outputPath) { var tileset = TreeSerializer.ToTileset(tree, translation); var s = JsonConvert.SerializeObject(tileset, Formatting.Indented, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); File.WriteAllText($"{outputPath}/tileset.json", s); }
public void StringColumn_LongValuesAndMerging() { StringColumn column = new StringColumn(); List <string> expected = new List <string>(); StringColumn roundTripped; // Test values just at and above LargeValue limit expected.Add(new string(' ', 2047)); expected.Add(null); expected.Add(string.Empty); expected.Add("Normal"); expected.Add(new string(' ', 2048)); for (int i = 0; i < expected.Count; ++i) { column[i] = expected[i]; } // Verify values properly captured CollectionReadVerifier.VerifySame(expected, column); // Proactively Trim (before serialization) and verify values not corrupted column.Trim(); CollectionReadVerifier.VerifySame(expected, column); // Verify roundtripped column and column not corrupted by serialization roundTripped = TreeSerializer.RoundTrip(column, TreeFormat.Binary); CollectionReadVerifier.VerifySame(expected, roundTripped); CollectionReadVerifier.VerifySame(expected, column); // Set a short value to long and a long value to short, and add another value expected[0] = new string(':', 2400); expected[2] = "MuchShorter"; expected.Add("Simple"); for (int i = 0; i < expected.Count; ++i) { column[i] = expected[i]; } // Verify values read back correctly immediately CollectionReadVerifier.VerifySame(expected, column); // Verify values re-roundtrip again properly (merging old and new immutable values) roundTripped = TreeSerializer.RoundTrip(column, TreeFormat.Binary); CollectionReadVerifier.VerifySame(expected, roundTripped); CollectionReadVerifier.VerifySame(expected, column); // Add a value causing a gap; verify count, new value returned, values in gap defaulted properly column[100] = "Centennial"; Assert.Equal(101, column.Count); Assert.Equal("Centennial", column[100]); Assert.Null(column[99]); }
public void StringColumn_EmptyCases() { // StringColumns are extremely common in object models, // so having very compact representations for common cases // is really important to file size for small databases. StringColumn column = new StringColumn(); TreeDiagnostics diagnostics; // Empty: { } diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(diagnostics.Length <= 2); // All null: { IsNull: { Count: 100, Capacity: 100 } } for (int i = 0; i < 100; ++i) { column[i] = null; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 13); // All empty: Only nulls false written for (int i = 0; i < 100; ++i) { column[i] = ""; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 13); // No nulls, No Empty: 3b / value (2b end + 1b text) + 4 pages x 4b + 20b overhead for (int i = 0; i < 100; ++i) { column[i] = "-"; } CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(1 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 336); // Nulls and Non-Nulls; both parts must be written column[50] = null; CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false)); diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); Assert.True(2 == diagnostics.Children.Count); Assert.True(diagnostics.Length <= 336 + 40); }
public void DistinctColumn_Conversion() { int defaultValue = -1; Func <DistinctColumn <int> > ctor = () => new DistinctColumn <int>(new NumberColumn <int>(defaultValue), defaultValue); DistinctColumn <int> column = ctor(); List <int> expected = new List <int>(); // Verify empty and set up to map values by default Assert.Empty(column); Assert.True(column.IsMappingValues); // Round trip and verify it resets back to mapping correctly column = TreeSerializer.RoundTrip(column, ctor, TreeFormat.Binary); Assert.Empty(column); Assert.True(column.IsMappingValues); // Add 1,000 values with 10 distinct values for (int i = 0; i < 1000; ++i) { int value = i % 10; column[i] = value; expected.Add(value); } // Verify column is mapping, has 11 unique values (default + 10), and matches expected array Assert.True(column.IsMappingValues); Assert.Equal(11, column.DistinctCount); CollectionReadVerifier.VerifySame(expected, column); // Round trip; verify mapped column rehydrates properly column = TreeSerializer.RoundTrip(column, ctor, TreeFormat.Binary); CollectionReadVerifier.VerifySame(expected, column); // Add enough values to force the column to convert for (int i = 1000; i < 1300; ++i) { column[i] = i; expected.Add(i); } Assert.False(column.IsMappingValues); Assert.Equal(-1, column.DistinctCount); CollectionReadVerifier.VerifySame(expected, column); // Round-trip; verify individual values column rehydrates properly column = TreeSerializer.RoundTrip(column, ctor, TreeFormat.Binary); CollectionReadVerifier.VerifySame(expected, column); // Test RemoveFromEnd on unmapped form of column column.RemoveFromEnd(100); expected.RemoveRange(expected.Count - 100, 100); CollectionReadVerifier.VerifySame(expected, column); }
public void Database_Basics() { V1.Community community = new V1.Community(); community.People = new List <V1.Person>(); community.People.Add(new V1.Person() { Age = 39, Name = "Scott" }); community.People.Add(new V1.Person() { Age = 36, Name = "Adam" }); // Use ReadOnlyList.VerifySame to check count, enumerators, and indexer community.DB.Save("V1.Community.bsoa", TreeFormat.Binary); V1.Community roundTripped = new V1.Community(); roundTripped.DB.Load("V1.Community.bsoa", TreeFormat.Binary); CollectionReadVerifier.VerifySame(community.People, roundTripped.People); // Try loading database with size diagnostics TreeDiagnostics diagnostics = TreeSerializer.Diagnostics(community.DB, () => new V1.Community().DB, TreeFormat.Binary); // Verify table and column names in diagnostics string text = diagnostics.ToString(); Assert.Contains("Person", text); Assert.Contains("Age", text); Assert.Contains("Name", text); // Verify Person has two columns, Write doesn't throw Assert.Equal("Person", diagnostics.Children[0].Name); Assert.Equal(Names.Columns, diagnostics.Children[0].Children[0].Name); Assert.Equal(2, diagnostics.Children[0].Children[0].Children.Count); diagnostics.Write(Console.Out, 3); // Verify Trim doesn't throw (results not visible) community.DB.Trim(); CollectionReadVerifier.VerifySame(community.People, roundTripped.People); // Verify Copy constructor recursively copies (List.SetTo -> LocalIndex -> CopyFrom construction) V1.Community copy = new V1.Community(community); CollectionReadVerifier.VerifySame(community.People, copy.People); community.People[0].Age += 10; Assert.NotEqual(community.People[0].Age, copy.People[0].Age); // Verify Database.Clear works community.DB.Clear(); var people = community.People; Assert.True(people == null || people.Count == 0); }
public void NumberListColumn_TypeList_Basics() { // Set up column with sample values, roundtrip, re-verify NumberListColumn <int> column = BuildSampleColumn(); // Verify second value is in a shared array, not at index zero, not expandable (yet), not ReadOnly NumberList <int> row1List = column[1]; TypedList <int> row1Typed = new TypedList <int>(row1List, (index) => index, (index) => index); // Test second sample row slice IList members on NumberListConverter CollectionChangeVerifier.VerifyList(row1Typed, (index) => index % 20); // Verify values are re-merged and re-loaded properly string values = string.Join(", ", row1List); column = TreeSerializer.RoundTrip(column, TreeFormat.Binary); Assert.Equal(values, string.Join(", ", column[1])); // TypedList Equality TypedList <int> row1 = new TypedList <int>(column[1], (index) => index, (index) => index); TypedList <int> row0 = new TypedList <int>(column[0], (index) => index, (index) => index); Assert.True(row1.Equals(row1)); Assert.False(row1.Equals(row0)); Assert.False(row1 == row0); Assert.True(row1 != row0); Assert.False(null == row0); Assert.True(null != row0); Assert.Equal(row1.GetHashCode(), row1.GetHashCode()); // TypedList.Indices Assert.Equal(row1.Indices, column[1]); // SetTo(other) TypedList <int> firstRow = new TypedList <int>(column[0], (index) => index, (index) => index); row1Typed.SetTo(firstRow); Assert.Equal(string.Join(", ", firstRow), string.Join(", ", row1Typed)); // SetTo(null) row1Typed.SetTo(null); Assert.Empty(row1Typed); // SetTo(IList) row1Typed.SetTo(new int[] { 2, 3, 4, 5 }); Assert.Equal("2, 3, 4, 5", string.Join(", ", row1Typed)); // SetTo(empty) row1Typed.SetTo(Array.Empty <int>()); Assert.Empty(row1Typed); }
public void BooleanColumn_AllDefault() { BooleanColumn c = new BooleanColumn(true); // Set a large number of values all to the default for (int i = 0; i < 8192; ++i) { c[i] = true; } // Verify the column serializes small (not to one bit per row) TreeDiagnostics diagnostics = TreeSerializer.Diagnostics(c, () => new BooleanColumn(true), TreeFormat.Binary); Assert.True(diagnostics.Length < 100); }
public void SerializeToJsonTest() { // arrange var t0 = new Tile(0, new Wkx.BoundingBox()); var t1 = new Tile(1, new Wkx.BoundingBox()); var tiles = new List <Tile> { t0, t1 }; var translation = new double[] { -8406745.0078531764, 4744614.2577285888, 38.29 }; var bbox = new double[] { 0, 0, 1, 1 }; // act var json = TreeSerializer.ToJson(tiles, translation, bbox, 500, "replace"); var jsonobject = JObject.Parse(json); Assert.IsTrue(jsonobject != null); }
public void SerializeTree() { // arrange var t0 = new Tile(0, new Wkx.BoundingBox()); var t1 = new Tile(1, new Wkx.BoundingBox()); var tiles = new List <Tile> { t0, t1 }; // act var translation = new double[] { -8406745.0078531764, 4744614.2577285888, 38.29 }; var bbox = new double[] { 0, 0, 1, 1 }; // assert var tileset = TreeSerializer.ToTileset(tiles, translation, bbox, 500, "replace"); Assert.IsTrue(tileset.root.children.Count == 2); }
public void NumberListColumn_Basics() { NumberListColumn <int> column = new NumberListColumn <int>(); column[0].SetTo(new ArraySlice <int>(new int[] { 0, 1, 2 })); TreeDiagnostics diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); int tinyColumnLength = (int)diagnostics.Length; Column.Basics(() => new NumberListColumn <int>(), NumberList <int> .Empty, column[0], (index) => { column[index].SetTo(new ArraySlice <int>(new int[] { index, index + 1, index + 2 })); return(column[index]); }); // NumberList Equals, GetHashCode int[] asArray = new int[] { 0, 1, 3 }; column[1].SetTo(new ArraySlice <int>(asArray)); Assert.False(column[0] == column[1]); Assert.True(column[0] != column[1]); Assert.NotEqual(column[0].GetHashCode(), column[1].GetHashCode()); // Compare to array Assert.True(column[1].Equals(asArray)); // ForEach column.Clear(); column[0].SetTo(new ArraySlice <int>(new int[] { 0, 1, 2 })); int sum = 0; column.ForEach((slice) => { int[] array = slice.Array; int end = slice.Index + slice.Count; for (int i = slice.Index; i < end; ++i) { sum += array[i]; } }); Assert.Equal(3, sum); }
public void TestSerialization() { Student peter = new Student("Peter", TestName.Physics, 7); Student john = new Student("John", TestName.Geography, 7); Student frank = new Student("Frank", TestName.Economics, 8); Student robert = new Student("Robert", TestName.Economics, 10); Tree <Student> tree = new Tree <Student>(); tree.Add(peter); tree.Add(john); tree.Add(frank); tree.Add(robert); string path = AppDomain.CurrentDomain.BaseDirectory + "/binaryTree.xml"; TreeSerializer.Serialize(tree, path); Tree <Student> treeDeser = TreeSerializer.Deserialize(path); Assert.AreEqual(tree.Count, treeDeser.Count); }
public void GenericNumberListColumn_Basics() { List <int> empty = new List <int>(); GenericNumberListColumn <int> column = new GenericNumberListColumn <int>(); column[0] = new int[] { 0, 1, 2 }; TreeDiagnostics diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary); int tinyColumnLength = (int)diagnostics.Length; Column.Basics(() => new GenericNumberListColumn <int>(), null, column[0], (index) => { IList <int> values = column[index]; if (values == null || values.Count == 0) { column[index] = new int[] { index, index + 1, index + 2 }; values = column[index]; } return(values); }); // ForEach column.Clear(); column[0] = new int[] { 0, 1, 2 }; int sum = 0; column.ForEach((slice) => { int[] array = slice.Array; int end = slice.Index + slice.Count; for (int i = slice.Index; i < end; ++i) { sum += array[i]; } }); Assert.Equal(3, sum); }
public void TreeReaderWriter_ExtensionMethods_Tests() { // DateTime and Guid serialization covered in TreeSerializable.Basics Random r = new Random(); Sample sample = new Sample(r); Sample sample2 = new Sample(r); // List and Dictionary serialization built-ins CollectionContainer <Sample> samples = new CollectionContainer <Sample>(); samples.Add(sample); samples.Add(sample2); samples.AssertEqual(TreeSerializer.RoundTrip(samples, TreeFormat.Json)); samples.AssertEqual(TreeSerializer.RoundTrip(samples, TreeFormat.Binary)); // Null List / Dictionary handling samples.SetCollectionsNull(); samples.AssertEqual(TreeSerializer.RoundTrip(samples, TreeFormat.Json)); samples.AssertEqual(TreeSerializer.RoundTrip(samples, TreeFormat.Binary)); }
public static void Basics <T>(Func <IColumn <T> > builder, T defaultValue, T otherValue, Func <int, T> valueProvider) { IColumn <T> column = builder(); List <T> expected = new List <T>(); // ICollection basics Assert.False(column.IsReadOnly); Assert.False(column.IsSynchronized); Assert.Null(column.SyncRoot); Assert.Equal(typeof(T), column.Type); // Empty behavior Assert.Equal(0, column.Count); Assert.Equal(defaultValue, column[0]); Assert.Equal(defaultValue, column[10]); Assert.Equal(0, column.Count); // Empty roundtrip works CollectionReadVerifier.VerifySame(expected, TreeSerializer.RoundTrip(column, builder, TreeFormat.Binary)); CollectionReadVerifier.VerifySame(expected, TreeSerializer.RoundTrip(column, builder, TreeFormat.Json)); // Empty trim works column.Trim(); // Append values for (int i = 0; i < 50; ++i) { T value = valueProvider(i); column.Add(value); expected.Add(value); } // Verify count, values, indexer, enumerators Assert.Equal(expected.Count, column.Count); CollectionReadVerifier.VerifySame <T>(expected, column); // Verify item type supports equatability (needed for IndexOf, Contains to work) Assert.True(otherValue.Equals(otherValue)); Assert.Equal(otherValue.GetHashCode(), otherValue.GetHashCode()); Assert.False(otherValue.Equals(defaultValue)); Assert.NotEqual(otherValue.GetHashCode(), defaultValue?.GetHashCode() ?? 0); Assert.False(otherValue.Equals(1.45d)); // Contains / IndexOf T notInList = valueProvider(50); if (!expected.Contains(notInList)) { Assert.DoesNotContain(notInList, column); Assert.Equal(-1, column.IndexOf(notInList)); } Assert.Contains(valueProvider(1), column); Assert.Equal(1, column.IndexOf(valueProvider(1))); // CopyTo T[] other = new T[column.Count + 1]; column.CopyTo(other, 1); Assert.Equal(column[0], other[1]); // CopyTo preconditions if (!Debugger.IsAttached) { Assert.Throws <ArgumentNullException>(() => column.CopyTo(null, 0)); Assert.Throws <ArgumentException>(() => column.CopyTo(other, 2)); Assert.Throws <ArgumentOutOfRangeException>(() => column.CopyTo(other, -1)); Assert.Throws <ArgumentException>(() => column.CopyTo((Array)(new decimal[column.Count]), 0)); } // CopyTo (untyped) other = new T[column.Count]; column.CopyTo((Array)other, 0); CollectionReadVerifier.VerifySame(other, column, quick: true); // Change existing value column[1] = otherValue; Assert.Equal(otherValue, column[1]); // Set value back to default, back to non-default column[1] = defaultValue; Assert.Equal(defaultValue, column[1]); column[1] = valueProvider(1); Assert.Equal(valueProvider(1), column[1]); // Add() without instance T item = column.Add(); Assert.Equal(defaultValue, item); // Copy values via CopyItem column.CopyItem(1, column, 2); Assert.Equal(column[1], column[2]); column[1] = valueProvider(1); // CopyItem type checking if (!Debugger.IsAttached) { Assert.Throws <ArgumentException>(() => column.CopyItem(1, new NumberColumn <Decimal>(0.0m), 0)); } // Append so resize is required column[100] = valueProvider(100); // Verify old values were kept, middle defaulted, last one set for (int i = 0; i < column.Count; ++i) { T value = (i < 50 || i == 100 ? valueProvider(i) : defaultValue); Assert.Equal(value, column[i]); } // Verify serialization round trip via all current serialization mechanisms CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, builder, TreeFormat.Binary), quick: true); CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, builder, TreeFormat.Json), quick: true); // Verify column is properly skippable (required to allow flexible file format schema) TreeSerializer.VerifySkip(column, TreeFormat.Binary); TreeSerializer.VerifySkip(column, TreeFormat.Json); // Verify original values are still there post-serialization (ensure column not corrupted by serialization) for (int i = 0; i < column.Count; ++i) { T value = (i < 50 || i == 100 ? valueProvider(i) : defaultValue); Assert.Equal(value, column[i]); } // Swap two non-default values, verify swapped, swap back column.Swap(10, 20); Assert.Equal(valueProvider(10), column[20]); Assert.Equal(valueProvider(20), column[10]); column.Swap(10, 20); Assert.Equal(valueProvider(10), column[10]); Assert.Equal(valueProvider(20), column[20]); // Swap a default with a non-default value, verify swapped, swap back column.Swap(30, 60); Assert.Equal(valueProvider(30), column[60]); Assert.Equal(defaultValue, column[30]); column.Swap(30, 60); Assert.Equal(valueProvider(30), column[30]); Assert.Equal(defaultValue, column[60]); // Verify RemoveFromEnd for only default values works column.RemoveFromEnd(column.Count - 60); Assert.Equal(60, column.Count); Assert.Equal(defaultValue, column[60]); // Verify RemoveFromEnd down to non-default values works column.RemoveFromEnd(60 - 10); Assert.Equal(10, column.Count); for (int i = 0; i < 60; ++i) { T value = (i < 10 ? valueProvider(i) : defaultValue); Assert.Equal(value, column[i]); } // Append a default value big enough another resize would be required // Verify the count is tracked correctly, previous items are initialized to default column[201] = defaultValue; Assert.Equal(202, column.Count); Assert.Equal(defaultValue, column[200]); // Verify serialization handles 'many defaults at end' properly CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, builder, TreeFormat.Binary), quick: true); // Verify Trim doesn't throw column.Trim(); // Verify clear resets count and that previously set values are back to default if accessed column.Clear(); Assert.Equal(0, column.Count); Assert.Equal(defaultValue, column[0]); Assert.Equal(defaultValue, column[1]); // Add one default value (inner array may still not be allocated), then try RemoveFromEnd column[0] = defaultValue; column.RemoveFromEnd(1); Assert.Equal(0, column.Count); if (!Debugger.IsAttached) { // Verify indexer range check (< 0 only; columns auto-size for bigger values) Assert.Throws <IndexOutOfRangeException>(() => column[-1]); // Verify Remove throws (not expected to be implemented) Assert.Throws <NotSupportedException>(() => column.Remove(defaultValue)); } }
public void JsonTreeReaderWriter_Basics() { // Run ITreeSerializable suite on JsonTreeReader and JsonTreeWriter TreeSerializer.Basics(TreeFormat.Json); }
public void ParserParseTest() { string path = (string)TestContext.DataRow["files"]; string testcontent = File.ReadAllText(path); string[] testparts = testcontent.Split(new string[] { "<<<TEST>>>" }, StringSplitOptions.RemoveEmptyEntries); Assert.IsTrue(testparts.Length >= 2); var sourceUnit = new CodeSourceUnit(testparts[0], path, Encoding.UTF8, Lexer.LexicalStates.INITIAL, LanguageFeatures.Basic); var factory = new BasicNodesFactory(sourceUnit); var errors = new TestErrorSink(); // sourceUnit.Parse(factory, errors, new TestErrorRecovery()); // if (testparts[1].TrimStart().StartsWith(Errors)) { var matches = _errorRegex.Matches(testparts[1]); var knownErrors = matches[0].Groups["Number"].Value.Split(','); Assert.AreEqual(1, matches.Count, path); Assert.AreEqual(knownErrors.Length, errors.Count, path); int errorid = 0; for (int i = 0; i < knownErrors.Length; i++) { Assert.IsTrue(int.TryParse(knownErrors[i], out errorid), path); Assert.AreEqual(errorid, errors.Errors[i].Error.Id, path); Assert.IsNotNull(errors.Errors[i].ToString()); } testparts[1] = matches[0].Groups["JSON"].Value; } else { Assert.AreEqual(0, errors.Count, path); } Assert.IsNotNull(sourceUnit.Ast); var serializer = new JsonNodeWriter(); TreeSerializer visitor = new TreeSerializer(serializer); sourceUnit.Ast.VisitMe(visitor); Regex rgx = new Regex(@"""Span""[^}]*},?\s*\n?"); // omit Span for more compact testing (position must be verified separately) string expected = rgx.Replace(testparts[1].Trim().Replace("\r", string.Empty).Replace("\n", string.Empty).Replace(" ", string.Empty), string.Empty); string actual = rgx.Replace(serializer.ToString().Replace("\r", string.Empty).Replace("\n", string.Empty).Replace(" ", string.Empty), string.Empty); if (testparts[1].Trim() != "<<<IGNORE>>>") { // IMPORTANT - Uncomment to regenerate test data //File.WriteAllText(path, testparts[0] + "\n<<<TEST>>>\n" + rgx.Replace(serializer.ToString(), string.Empty)); Assert.AreEqual(expected, actual, path); } // check every node has a parent var parentChecker = new ContainingElementCheck(); parentChecker.VisitGlobalCode(sourceUnit.Ast); // check nodes have correct span corresponding to correct source text var spanChecker = new NameSpanCheck(testparts[0]); spanChecker.VisitGlobalCode(sourceUnit.Ast); }
public static FileParserResult ParseFile(Text Text) { var TypeFunctions = new HashSet <String>() { "Primitive", "Alias", "Record", "TaggedUnion", "Enum", "ClientCommand", "ServerCommand" }; var Functions = new HashSet <String>(TypeFunctions.Concat(new List <String>() { "Namespace", "Import" })); var ps = new TreeFormatParseSetting() { IsTableParameterFunction = Name => Functions.Contains(Name), IsTableContentFunction = Name => Functions.Contains(Name), IsTreeParameterFunction = Name => false, IsTreeContentFunction = Name => false }; var sp = new TreeFormatSyntaxParser(ps, Text); var ParserResult = sp.Parse(); var ts = new TreeSerializer(); var Types = new List <TypeDef>(); var TypeRefs = new List <TypeDef>(); var Imports = new List <String>(); var TypeToNamespace = new Dictionary <TypeDef, List <String> >(); var TypeToNamespaceImports = new Dictionary <TypeDef, List <List <String> > >(); var CurrentNamespace = new List <String>(); var CurrentNamespaceImports = new List <List <String> >(); var Positions = new Dictionary <Object, TextRange>(); foreach (var TopNode in ParserResult.Value.MultiNodesList) { if (TopNode.OnFunctionNodes) { var pr = new TreeFormatParseResult { Value = new Forest { MultiNodesList = new List <MultiNodes> { TopNode } }, Text = Text, Positions = ParserResult.Positions, RawFunctionCalls = ParserResult.RawFunctionCalls }; var es = new TreeFormatEvaluateSetting { FunctionCallEvaluator = (f, nm) => { Action <Object, Object> Mark = (SemanticsObj, SyntaxObj) => { var Range = nm.GetRange(SyntaxObj); if (Range.OnSome) { Positions.Add(SemanticsObj, Range.Value); } }; Func <TFSemantics.Node, List <String> > ExtractNamespaceParts = Node => { var Namespace = GetLeafNodeValue(Node, nm, "InvalidName"); var NamespaceParts = new List <String>(); int InvalidCharIndex; var osml = TokenParser.TrySplitSymbolMemberChain(Namespace, out InvalidCharIndex); if (osml.OnNone) { var Range = nm.GetRange(Node); var InvalidChar = Namespace.Substring(InvalidCharIndex, 1); if (Range.OnSome) { Range = new TextRange { Start = nm.Text.Calc(Range.Value.Start, InvalidCharIndex), End = nm.Text.Calc(Range.Value.Start, InvalidCharIndex + 1) }; } throw new InvalidTokenException("InvalidChar", new FileTextRange { Text = nm.Text, Range = Range }, InvalidChar); } foreach (var p in osml.Value) { if (p.Parameters.Count > 0) { var Range = nm.GetRange(Node); var Part = Namespace.Substring(p.SymbolStartIndex, p.SymbolEndIndex); if (Range.OnSome) { Range = new TextRange { Start = nm.Text.Calc(Range.Value.Start, p.SymbolStartIndex), End = nm.Text.Calc(Range.Value.Start, p.SymbolEndIndex) }; } throw new InvalidTokenException("InvalidNamespacePart", new FileTextRange { Text = nm.Text, Range = Range }, Part); } int LocalInvalidCharIndex; var oName = TokenParser.TryUnescapeSymbolName(p.Name, out LocalInvalidCharIndex); if (oName.OnNone) { InvalidCharIndex = p.NameStartIndex + LocalInvalidCharIndex; var Range = nm.GetRange(Node); var InvalidChar = Namespace.Substring(InvalidCharIndex, 1); if (Range.OnSome) { Range = new TextRange { Start = nm.Text.Calc(Range.Value.Start, InvalidCharIndex), End = nm.Text.Calc(Range.Value.Start, InvalidCharIndex + 1) }; } throw new InvalidTokenException("InvalidChar", new FileTextRange { Text = nm.Text, Range = Range }, InvalidChar); } NamespaceParts.Add(p.Name); } return(NamespaceParts); }; if (TypeFunctions.Contains(f.Name.Text)) { if (f.Parameters.Count < 1 || f.Parameters.Count > 2) { throw new InvalidEvaluationException("InvalidParameterCount", nm.GetFileRange(f), f); } var TypeRef = ParseTypeRef(f.Parameters[0], nm, Positions); var Name = TypeRef.Name; var Version = TypeRef.Version; var Attributes = new List <KeyValuePair <String, List <String> > >(); var Description = ""; if (f.Parameters.Count >= 2) { var DescriptionParameter = f.Parameters[1]; if (!DescriptionParameter.OnLeaf) { throw new InvalidEvaluationException("InvalidDescription", nm.GetFileRange(DescriptionParameter), DescriptionParameter); } var c = TokenParser.DecomposeDescription(DescriptionParameter.Leaf); Attributes = c.Attributes; Mark(Attributes, f.Parameters[1]); Description = c.Description; } var ContentLines = new List <FunctionCallTableLine> { }; if (f.Content.OnSome) { var ContentValue = f.Content.Value; if (!ContentValue.OnTableContent) { throw new InvalidEvaluationException("InvalidContent", nm.GetFileRange(ContentValue), ContentValue); } ContentLines = ContentValue.TableContent; } switch (f.Name.Text) { case "Primitive": { if (Version != "") { throw new InvalidEvaluationException("InvalidName", nm.GetFileRange(f.Parameters[0]), f.Parameters[0]); } var GenericParameters = new List <VariableDef>(); foreach (var Line in ContentLines) { String cName = null; TypeSpec cType = null; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); var c = TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } if (cName.StartsWith("'")) { cName = new String(cName.Skip(1).ToArray()); var gp = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription }; Mark(gp, Line); GenericParameters.Add(gp); } else { throw new InvalidEvaluationException("InvalidLine", nm.GetFileRange(Line), Line); } } var p = new PrimitiveDef { Name = Name, GenericParameters = GenericParameters, Attributes = Attributes, Description = Description }; Mark(p, f); var t = TypeDef.CreatePrimitive(p); Mark(t, f); Types.Add(t); TypeToNamespace.Add(t, CurrentNamespace); TypeToNamespaceImports.Add(t, CurrentNamespaceImports); return(new List <TFSemantics.Node> { }); } case "Alias": { var GenericParameters = new List <VariableDef>(); TypeSpec Type = null; foreach (var Line in ContentLines) { String cName = null; TypeSpec cType = null; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 1) { if (Type != null) { throw new InvalidEvaluationException("InvalidLine", nm.GetFileRange(Line), Line); } Type = ParseTypeSpec(Line.Nodes[0], nm, Positions); continue; } else if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); var c = TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } if (cName.StartsWith("'")) { cName = new String(cName.Skip(1).ToArray()); var gp = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription }; Mark(gp, Line); GenericParameters.Add(gp); } else { throw new InvalidEvaluationException("InvalidLine", nm.GetFileRange(Line), Line); } } if (Type == null) { throw new InvalidEvaluationException("InvalidContent", nm.GetFileRange(ContentLines), ContentLines); } var a = new AliasDef { Name = Name, Version = Version, GenericParameters = GenericParameters, Type = Type, Attributes = Attributes, Description = Description }; Mark(a, f); var t = TypeDef.CreateAlias(a); Mark(t, f); Types.Add(t); TypeToNamespace.Add(t, CurrentNamespace); TypeToNamespaceImports.Add(t, CurrentNamespaceImports); return(new List <TFSemantics.Node> { }); } case "Record": { var GenericParameters = new List <VariableDef>(); var Fields = new List <VariableDef>(); foreach (var Line in ContentLines) { String cName = null; TypeSpec cType = null; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); var c = TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } if (cName.StartsWith("'")) { cName = new String(cName.Skip(1).ToArray()); var gp = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription }; Mark(gp, Line); GenericParameters.Add(gp); } else { var p = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription }; Mark(p, Line); Fields.Add(p); } } var r = new RecordDef { Name = Name, Version = Version, GenericParameters = GenericParameters, Fields = Fields, Attributes = Attributes, Description = Description }; Mark(r, f); var t = TypeDef.CreateRecord(r); Mark(t, f); Types.Add(t); TypeToNamespace.Add(t, CurrentNamespace); TypeToNamespaceImports.Add(t, CurrentNamespaceImports); return(new List <TFSemantics.Node> { }); } case "TaggedUnion": { var GenericParameters = new List <VariableDef>(); var Alternatives = new List <VariableDef>(); foreach (var Line in ContentLines) { String cName = null; TypeSpec cType = null; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidAlternativeName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidAlternativeName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); var c = TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } if (cName.StartsWith("'")) { cName = new String(cName.Skip(1).ToArray()); var gp = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription }; Mark(gp, Line); GenericParameters.Add(gp); } else { var p = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription }; Mark(p, Line); Alternatives.Add(p); } } var tu = new TaggedUnionDef { Name = Name, Version = Version, GenericParameters = GenericParameters, Alternatives = Alternatives, Attributes = Attributes, Description = Description }; Mark(tu, f); var t = TypeDef.CreateTaggedUnion(tu); Mark(t, f); Types.Add(t); TypeToNamespace.Add(t, CurrentNamespace); TypeToNamespaceImports.Add(t, CurrentNamespaceImports); return(new List <TFSemantics.Node> { }); } case "Enum": { var Literals = new List <LiteralDef>(); Int64 NextValue = 0; foreach (var Line in ContentLines) { String cName = null; Int64 cValue = NextValue; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 1) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidLiteralName"); cValue = NextValue; } else if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidLiteralName"); cValue = NumericStrings.InvariantParseInt64(GetLeafNodeValue(Line.Nodes[1], nm, "InvalidLiteralValue")); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidLiteralName"); cValue = NumericStrings.InvariantParseInt64(GetLeafNodeValue(Line.Nodes[1], nm, "InvalidLiteralValue")); var c = TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } NextValue = cValue + 1; var ltl = new LiteralDef { Name = cName, Value = cValue, Attributes = cAttributes, Description = cDescription }; Mark(ltl, Line); Literals.Add(ltl); } var IntTypeName = new List <String> { "Int" }; Mark(IntTypeName, f); var r = new TypeRef { Name = IntTypeName, Version = "" }; Mark(r, f); var UnderlyingType = TypeSpec.CreateTypeRef(r); Mark(UnderlyingType, f); var ed = new EnumDef { Name = Name, Version = Version, UnderlyingType = UnderlyingType, Literals = Literals, Attributes = Attributes, Description = Description }; Mark(ed, f); var t = TypeDef.CreateEnum(ed); Mark(t, f); Types.Add(t); TypeToNamespace.Add(t, CurrentNamespace); TypeToNamespaceImports.Add(t, CurrentNamespaceImports); return(new List <TFSemantics.Node> { }); } case "ClientCommand": { var OutParameters = new List <VariableDef>(); var InParameters = new List <VariableDef>(); Boolean IsInParameter = false; foreach (var Line in ContentLines) { String cName = null; TypeSpec cType = null; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 1) { if (GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName") == ">") { IsInParameter = true; continue; } else { throw new InvalidEvaluationException("InvalidLine", nm.GetFileRange(Line), Line); } } else if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); var c = TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } var p = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription }; Mark(p, Line); if (IsInParameter) { InParameters.Add(p); } else { OutParameters.Add(p); } } var cc = new ClientCommandDef { Name = Name, Version = Version, OutParameters = OutParameters, InParameters = InParameters, Attributes = Attributes, Description = Description }; Mark(cc, f); var t = TypeDef.CreateClientCommand(cc); Mark(t, f); Types.Add(t); TypeToNamespace.Add(t, CurrentNamespace); TypeToNamespaceImports.Add(t, CurrentNamespaceImports); return(new List <TFSemantics.Node> { }); } case "ServerCommand": { var OutParameters = new List <VariableDef>(); foreach (var Line in ContentLines) { String cName = null; TypeSpec cType = null; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); var c = TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } var p = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription }; Mark(p, Line); OutParameters.Add(p); } var sc = new ServerCommandDef { Name = Name, Version = Version, OutParameters = OutParameters, Attributes = Attributes, Description = Description }; Mark(sc, f); var t = TypeDef.CreateServerCommand(sc); Mark(t, f); Types.Add(t); TypeToNamespace.Add(t, CurrentNamespace); TypeToNamespaceImports.Add(t, CurrentNamespaceImports); return(new List <TFSemantics.Node> { }); } default: { throw new InvalidEvaluationException("UnknownFunction", nm.GetFileRange(f), f); } } } else if (f.Name.Text == "Namespace") { if (f.Parameters.Count != 1) { throw new InvalidEvaluationException("InvalidParameterCount", nm.GetFileRange(f), f); } var NamespaceParts = ExtractNamespaceParts(f.Parameters[0]); Mark(NamespaceParts, f); CurrentNamespace = NamespaceParts; return(new List <TFSemantics.Node> { }); } else if (f.Name.Text == "Import") { if (f.Parameters.Count != 0) { throw new InvalidEvaluationException("InvalidParameterCount", nm.GetFileRange(f), f); } var ContentLines = new List <FunctionCallTableLine> { }; if (f.Content.OnSome) { var ContentValue = f.Content.Value; if (!ContentValue.OnTableContent) { throw new InvalidEvaluationException("InvalidContent", nm.GetFileRange(ContentValue), ContentValue); } ContentLines = ContentValue.TableContent; } var NamespaceImports = new List <List <String> >(); foreach (var Line in ContentLines) { if (Line.Nodes.Count == 1) { var NamespaceParts = ExtractNamespaceParts(Line.Nodes[0]); Mark(NamespaceParts, Line.Nodes[0]); NamespaceImports.Add(NamespaceParts); } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } } CurrentNamespaceImports = CurrentNamespaceImports.Concat(NamespaceImports).ToList(); return(new List <TFSemantics.Node> { }); } else { throw new InvalidEvaluationException("UnknownFunction", nm.GetFileRange(f), f); } } }; var e = new TreeFormatEvaluator(es, pr); e.Evaluate(); } else { var pr = new TreeFormatParseResult { Value = new Forest { MultiNodesList = new List <MultiNodes> { TopNode } }, Text = Text, Positions = ParserResult.Positions, RawFunctionCalls = ParserResult.RawFunctionCalls }; var es = new TreeFormatEvaluateSetting { }; var e = new TreeFormatEvaluator(es, pr); var er = e.Evaluate(); if (er.Value.Nodes.Count > 0) { var ReadResult = ts.Read <Schema>(CollectionOperations.CreatePair(er.Value, er.Positions)); Types.AddRange(ReadResult.Key.Types); TypeRefs.AddRange(ReadResult.Key.TypeRefs); Imports.AddRange(ReadResult.Key.Imports); foreach (var p in ReadResult.Value) { if (p.Value.Range.OnSome) { Positions.Add(p.Key, p.Value.Range.Value); } } } } } return(new FileParserResult { Text = Text, Positions = Positions, Types = Types, TypeRefs = TypeRefs, Imports = Imports, TypeToNamespace = TypeToNamespace, TypeToNamespaceImports = TypeToNamespaceImports }); }
public static FileParserResult ParseFile(Text Text) { var TypeFunctions = new HashSet <String>() { "Primitive", "Entity", "Enum" }; var TableParameterFunctions = new HashSet <String>(TypeFunctions.Concat(new List <String> { "Query" })); var TableContentFunctions = TypeFunctions; var ps = new TreeFormatParseSetting() { IsTableParameterFunction = Name => TableParameterFunctions.Contains(Name), IsTableContentFunction = Name => TableContentFunctions.Contains(Name) }; var sp = new TreeFormatSyntaxParser(ps, Text); var ParserResult = sp.Parse(); var ts = new TreeSerializer(); var Types = new List <TypeDef>(); var TypeRefs = new List <TypeDef>(); var Imports = new List <String>(); var Positions = new Dictionary <Object, TextRange>(); foreach (var TopNode in ParserResult.Value.MultiNodesList) { if (TopNode.OnFunctionNodes) { var pr = new TreeFormatParseResult { Value = new Forest { MultiNodesList = new List <MultiNodes> { TopNode } }, Text = Text, Positions = ParserResult.Positions, RawFunctionCalls = ParserResult.RawFunctionCalls }; var es = new TreeFormatEvaluateSetting { FunctionCallEvaluator = (f, nm) => { Action <Object, Object> Mark = (SemanticsObj, SyntaxObj) => { var Range = nm.GetRange(SyntaxObj); if (Range.OnSome) { Positions.Add(SemanticsObj, Range.Value); } }; if (TypeFunctions.Contains(f.Name.Text)) { if (f.Parameters.Count < 1 || f.Parameters.Count > 2) { throw new InvalidEvaluationException("InvalidParameterCount", nm.GetFileRange(f), f); } var VersionedName = GetLeafNodeValue(f.Parameters[0], nm, "InvalidName"); var TypeRef = ParseTypeRef(VersionedName); Mark(TypeRef, f.Parameters[0]); var Name = (String)TypeRef; var Attributes = new List <KeyValuePair <String, List <String> > >(); var Description = ""; if (f.Parameters.Count >= 2) { var DescriptionParameter = f.Parameters[1]; if (!DescriptionParameter.OnLeaf) { throw new InvalidEvaluationException("InvalidDescription", nm.GetFileRange(DescriptionParameter), DescriptionParameter); } var c = OS.TokenParser.DecomposeDescription(DescriptionParameter.Leaf); Attributes = c.Attributes; Mark(Attributes, f.Parameters[1]); Description = c.Description; } var ContentLines = new List <FunctionCallTableLine> { }; if (f.Content.OnSome) { var ContentValue = f.Content.Value; if (!ContentValue.OnTableContent) { throw new InvalidEvaluationException("InvalidContent", nm.GetFileRange(ContentValue), ContentValue); } ContentLines = ContentValue.TableContent; } switch (f.Name.Text) { case "Primitive": { var GenericParameters = new List <VariableDef>(); foreach (var Line in ContentLines) { String cName = null; TypeSpec cType = null; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); var c = OS.TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } if (cName.StartsWith("'")) { cName = new String(cName.Skip(1).ToArray()); var gp = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription, Attribute = null }; Mark(gp, Line); GenericParameters.Add(gp); } else { throw new InvalidEvaluationException("InvalidLine", nm.GetFileRange(Line), Line); } } var p = new PrimitiveDef { Name = Name, Attributes = Attributes, Description = Description }; Mark(p, f); var t = TypeDef.CreatePrimitive(p); Mark(t, f); Types.Add(t); return(new List <TFSemantics.Node> { }); } case "Entity": { var Fields = new List <VariableDef>(); foreach (var Line in ContentLines) { String cName = null; TypeSpec cType = null; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidFieldName"); cType = ParseTypeSpec(Line.Nodes[1], nm, Positions); var c = OS.TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } var p = new VariableDef { Name = cName, Type = cType, Attributes = cAttributes, Description = cDescription }; Mark(p, Line); Fields.Add(p); } var ed = new EntityDef { Name = Name, Fields = Fields, Attributes = Attributes, Description = Description }; Mark(ed, f); var t = TypeDef.CreateEntity(ed); Mark(t, f); Types.Add(t); return(new List <TFSemantics.Node> { }); } case "Enum": { var Literals = new List <LiteralDef>(); Int64 NextValue = 0; foreach (var Line in ContentLines) { String cName = null; Int64 cValue = NextValue; var cAttributes = new List <KeyValuePair <String, List <String> > >(); var cDescription = ""; if (Line.Nodes.Count == 1) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidLiteralName"); cValue = NextValue; } else if (Line.Nodes.Count == 2) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidLiteralName"); cValue = NumericStrings.InvariantParseInt64(GetLeafNodeValue(Line.Nodes[1], nm, "InvalidLiteralValue")); } else if (Line.Nodes.Count == 3) { cName = GetLeafNodeValue(Line.Nodes[0], nm, "InvalidLiteralName"); cValue = NumericStrings.InvariantParseInt64(GetLeafNodeValue(Line.Nodes[1], nm, "InvalidLiteralValue")); var c = OS.TokenParser.DecomposeDescription(GetLeafNodeValue(Line.Nodes[2], nm, "InvalidDescription")); cAttributes = c.Attributes; Mark(cAttributes, Line.Nodes[2]); cDescription = c.Description; } else if (Line.Nodes.Count == 0) { continue; } else { throw new InvalidEvaluationException("InvalidLineNodeCount", nm.GetFileRange(Line), Line); } NextValue = cValue + 1; var ltl = new LiteralDef { Name = cName, Value = cValue, Attributes = cAttributes, Description = cDescription }; Mark(ltl, Line); Literals.Add(ltl); } var r = (TypeRef)("Int"); Mark(r, f); var UnderlyingType = TypeSpec.CreateTypeRef(r); Mark(UnderlyingType, f); var ed = new EnumDef { Name = Name, UnderlyingType = UnderlyingType, Literals = Literals, Attributes = Attributes, Description = Description }; Mark(ed, f); var t = TypeDef.CreateEnum(ed); Mark(t, f); Types.Add(t); return(new List <TFSemantics.Node> { }); } default: { throw new InvalidEvaluationException("UnknownFunction", nm.GetFileRange(f), f); } } } else if (f.Name.Text == "Query") { if (f.Parameters.Count != 0) { throw new InvalidEvaluationException("InvalidParameterCount", nm.GetFileRange(f), f); } var Verbs = new Dictionary <String, Func <Verb> > { { "Select", () => Verb.CreateSelect() }, { "Lock", () => Verb.CreateLock() }, { "Insert", () => Verb.CreateInsert() }, { "Update", () => Verb.CreateUpdate() }, { "Upsert", () => Verb.CreateUpsert() }, { "Delete", () => Verb.CreateDelete() } }; var Numerals = new Dictionary <String, Func <Numeral> > { { "Optional", () => Numeral.CreateOptional() }, { "One", () => Numeral.CreateOne() }, { "Many", () => Numeral.CreateMany() }, { "All", () => Numeral.CreateAll() }, { "Range", () => Numeral.CreateRange() }, { "Count", () => Numeral.CreateCount() } }; Func <int, TextLine, List <QueryDef> > ParseQueryDef = (IndentLevel, Line) => { var l = new List <TFSemantics.Node>(); List <TFSemantics.Node> cl = null; TextPosition clStart = default(TextPosition); TextPosition clEnd = default(TextPosition); if (Line.Text.Length < IndentLevel * 4) { return(new List <QueryDef> { }); } var LineRange = new TextRange { Start = Text.Calc(Line.Range.Start, IndentLevel * 4), End = Line.Range.End }; var Range = LineRange; while (true) { var tpr = TreeFormatTokenParser.ReadToken(Text, pr.Positions, Range); if (!tpr.OnSome) { break; } var v = tpr.Value.Token; if (v.OnSingleLineComment) { break; } if (v.OnLeftParenthesis) { if (cl != null) { throw new InvalidTokenException("DoubleLeftParenthesis", new FileTextRange { Text = Text, Range = Range }, "("); } cl = new List <TFSemantics.Node>(); clStart = Range.Start; clEnd = Range.End; } else if (v.OnRightParenthesis) { if (cl == null) { throw new InvalidTokenException("DismatchedRightParenthesis", new FileTextRange { Text = Text, Range = Range }, ")"); } if (cl.Count == 0) { throw new InvalidTokenException("EmptyIndex", new FileTextRange { Text = Text, Range = Range }, ")"); } if (tpr.Value.RemainingChars.OnSome) { clEnd = tpr.Value.RemainingChars.Value.End; } l.Add(nm.MakeStemNode("", cl, new TextRange { Start = clStart, End = clEnd })); cl = null; clStart = default(TextPosition); clEnd = default(TextPosition); } else if (v.OnSingleLineLiteral) { if (cl != null) { cl.Add(nm.MakeLeafNode(v.SingleLineLiteral, pr.Positions[v])); } else { l.Add(nm.MakeLeafNode(v.SingleLineLiteral, pr.Positions[v])); } } else { throw new InvalidTokenException("UnknownToken", new FileTextRange { Text = Text, Range = Range }, Text.GetTextInLine(Range)); } if (!tpr.Value.RemainingChars.OnSome) { break; } Range = tpr.Value.RemainingChars.Value; } if (cl != null) { throw new InvalidTokenException("DismatchedRightParentheses", new FileTextRange { Text = Text, Range = Range }, ""); } if (l.Count == 0) { return(new List <QueryDef> { }); } if (l.Count != 4 && l.Count != 6 && l.Count != 8) { throw new InvalidSyntaxException("InvalidQuery", new FileTextRange { Text = Text, Range = LineRange }); } var From = GetLeafNodeValue(l[0], nm, "InvalidFrom"); if (From != "From") { throw new InvalidTokenException("InvalidFrom", nm.GetFileRange(l[0]), From); } var EntityName = GetLeafNodeValue(l[1], nm, "InvalidEntityName"); var VerbName = GetLeafNodeValue(l[2], nm, "InvalidVerb"); if (!Verbs.ContainsKey(VerbName)) { throw new InvalidTokenException("InvalidVerb", nm.GetFileRange(l[2]), VerbName); } var Verb = Verbs[VerbName](); Mark(Verb, l[2]); var NumeralName = GetLeafNodeValue(l[3], nm, "InvalidNumeral"); if (!Numerals.ContainsKey(NumeralName)) { throw new InvalidTokenException("InvalidNumeral", nm.GetFileRange(l[3]), NumeralName); } var Numeral = Numerals[NumeralName](); Mark(Numeral, l[3]); var By = new List <String> { }; var OrderBy = new List <KeyColumn> { }; if (l.Count >= 6) { var ByOrOrderByName = GetLeafNodeValue(l[4], nm, "InvalidByOrOrderBy"); if (ByOrOrderByName == "By") { if (l[5].OnLeaf) { By = new List <String> { l[5].Leaf }; } else if (l[5].OnStem) { By = l[5].Stem.Children.Select(c => GetLeafNodeValue(c, nm, "InvalidKeyColumn")).ToList(); } else { throw new InvalidSyntaxException("InvalidBy", nm.GetFileRange(l[5])); } Mark(By, l[5]); } else if (ByOrOrderByName == "OrderBy") { if (l[5].OnLeaf) { OrderBy = (new List <String> { l[5].Leaf }).Select(c => c.EndsWith("-") ? new KeyColumn { Name = c.Substring(0, c.Length - 1), IsDescending = true } : new KeyColumn { Name = c, IsDescending = false }).ToList(); } else if (l[5].OnStem) { OrderBy = l[5].Stem.Children.Select(c => GetLeafNodeValue(c, nm, "InvalidKeyColumn")).Select(c => c.EndsWith("-") ? new KeyColumn { Name = c.Substring(0, c.Length - 1), IsDescending = true } : new KeyColumn { Name = c, IsDescending = false }).ToList(); } else { throw new InvalidSyntaxException("InvalidOrderBy", nm.GetFileRange(l[5])); } Mark(OrderBy, l[5]); } else { throw new InvalidSyntaxException("InvalidByOrOrderBy", nm.GetFileRange(l[5])); } } if (l.Count >= 8) { if (OrderBy.Count != 0) { throw new InvalidSyntaxException("InvalidOrderBy", nm.GetFileRange(l[6])); } var OrderByName = GetLeafNodeValue(l[6], nm, "InvalidOrderBy"); if (OrderByName == "OrderBy") { if (l[7].OnLeaf) { OrderBy = (new List <String> { l[7].Leaf }).Select(c => c.EndsWith("-") ? new KeyColumn { Name = c.Substring(0, c.Length - 1), IsDescending = true } : new KeyColumn { Name = c, IsDescending = false }).ToList(); } else if (l[7].OnStem) { OrderBy = l[7].Stem.Children.Select(c => GetLeafNodeValue(c, nm, "InvalidKeyColumn")).Select(c => c.EndsWith("-") ? new KeyColumn { Name = c.Substring(0, c.Length - 1), IsDescending = true } : new KeyColumn { Name = c, IsDescending = false }).ToList(); } else { throw new InvalidSyntaxException("InvalidOrderBy", nm.GetFileRange(l[7])); } Mark(OrderBy, l[7]); } else { throw new InvalidSyntaxException("InvalidOrderBy", nm.GetFileRange(l[7])); } } var q = new QueryDef { EntityName = EntityName, Verb = Verb, Numeral = Numeral, By = By, OrderBy = OrderBy }; Mark(q, Line); return(new List <QueryDef> { q }); }; var Queries = f.Content.Value.LineContent.Lines.SelectMany(Line => ParseQueryDef(f.Content.Value.LineContent.IndentLevel, Line)).ToList(); Mark(Queries, f); var ql = new QueryListDef { Queries = Queries }; Mark(ql, f); var t = TypeDef.CreateQueryList(ql); Mark(t, f); Types.Add(t); return(new List <TFSemantics.Node> { }); } else { throw new InvalidEvaluationException("UnknownFunction", nm.GetFileRange(f), f); } } }; var e = new TreeFormatEvaluator(es, pr); e.Evaluate(); } else { var pr = new TreeFormatParseResult { Value = new Forest { MultiNodesList = new List <MultiNodes> { TopNode } }, Text = Text, Positions = ParserResult.Positions, RawFunctionCalls = ParserResult.RawFunctionCalls }; var es = new TreeFormatEvaluateSetting { }; var e = new TreeFormatEvaluator(es, pr); var er = e.Evaluate(); if (er.Value.Nodes.Count > 0) { var ReadResult = ts.Read <Schema>(CollectionOperations.CreatePair(er.Value, er.Positions)); Types.AddRange(ReadResult.Key.Types); TypeRefs.AddRange(ReadResult.Key.TypeRefs); Imports.AddRange(ReadResult.Key.Imports); foreach (var p in ReadResult.Value) { if (p.Value.Range.OnSome) { Positions.Add(p.Key, p.Value.Range.Value); } } } } } var Schema = new Schema { Types = Types, TypeRefs = TypeRefs, Imports = Imports }; return(new FileParserResult { Schema = Schema, Text = Text, Positions = Positions }); }