using System; using System.Collections.Generic; using System.Linq; using Sprache; public class SgfTree { public IDictionary Data { get; } public SgfTree[] Children { get; } public SgfTree(IDictionary data, params SgfTree[] children) { Data = data; Children = children; } } public static class SgfParser { private static char Unescape(char c) => c == 'n' ? '\n' : c == 'r' || c == 't' ? ' ' : c; private static string ToString(IEnumerable chars) => new string(chars.ToArray()); private static readonly Parser NormalChar = Parse.Char(c => c != '\\' && c != ']', "Normal char"); private static readonly Parser EscapedChar = Parse.Char('\\').Then(_ => Parse.AnyChar.Token().Select(Unescape)); private static readonly Parser CValueType = EscapedChar.Or(NormalChar).Many().Select(ToString); private static readonly Parser PropValue = CValueType.Contained(Parse.Char('['), Parse.Char(']')); private static readonly Parser PropIdent = Parse.Char(char.IsUpper, "Upper case character").Select(c => c.ToString()); private static readonly Parser> Property = PropIdent.Then(ident => PropValue.Many().Select(values => PropertyToData(ident, values))); private static readonly Parser> Node = Parse.Char(';').Then(_ => Property.Optional().Select(o => o.GetOrDefault() ?? new Dictionary())); private static Parser GameTree() => Parse.Char('(').Then(c => Node.AtLeastOnce().Then(nodes => GameTree().Many().Then(trees => Parse.Char(')') .Select(___ => NodesToTree(nodes, trees))))); public static SgfTree ParseTree(string input) { try { return GameTree().Parse(input); } catch (Exception e) { throw new ArgumentException(nameof(input), e); } } private static SgfTree NodesToTree(IEnumerable> properties, IEnumerable trees) { var numberOfProperties = properties.Count(); if (numberOfProperties == 0) { throw new InvalidOperationException("Can only create tree from non-empty nodes list"); } if (numberOfProperties == 1) { return new SgfTree(properties.First(), trees.ToArray()); } return new SgfTree(properties.First(), NodesToTree(properties.Skip(1), trees)); } private static IDictionary PropertyToData(string key, IEnumerable values) => new Dictionary { [key] = values.ToArray() }; }