using System.Collections.Generic; using Pidgin; using Robust.Shared.Maths; using static Pidgin.Parser; using static Pidgin.Parser; namespace Robust.Shared.Utility { public partial class FormattedMessage { // wtf I love parser combinators now. private const char TagBegin = '['; private const char TagEnd = ']'; private static readonly Parser ParseEscapeSequence = Char('\\').Then(OneOf( Char('\\'), Char(TagBegin), Char(TagEnd))); private static readonly Parser ParseTagText = ParseEscapeSequence.Or(Token(c => c != TagBegin && c != '\\')) .AtLeastOnceString() .Select(s => new TagText(s)); private static readonly Parser ParseTagColor = String("color") .Then(Char('=')) .Then(Token(ValidColorNameContents).AtLeastOnceString() .Select(s => { if (Color.TryFromName(s, out var color)) { return new TagColor(color); } return new TagColor(Color.FromHex(s)); })); private static readonly Parser ParseTagPop = Char('/') .Then(String("color")) .ThenReturn(TagPop.Instance); private static readonly Parser ParseTagContents = ParseTagColor.Cast().Or(ParseTagPop.Cast()); private static readonly Parser ParseEnclosedTag = ParseTagContents.Between(Char(TagBegin), Char(TagEnd)); private static readonly Parser ParseTagOrFallBack = Try(ParseEnclosedTag) // If we couldn't parse a tag then parse the [ of the start of the tag // so the rest is recognized as text. .Or(Char(TagBegin).ThenReturn(new TagText("["))); private static readonly Parser> Parse = ParseTagText.Cast().Or(ParseEnclosedTag).Many(); private static readonly Parser> ParsePermissive = ParseTagText.Cast().Or(ParseTagOrFallBack).Many(); public static bool ValidMarkup(string markup) { return Parse.Parse(markup).Success; } public void AddMarkup(string markup) { _tags.AddRange(Parse.ParseOrThrow(markup)); } /// /// Will parse invalid markup tags as text instead of ignoring them. /// public void AddMarkupPermissive(string markup) { _tags.AddRange(ParsePermissive.ParseOrThrow(markup)); } private static bool ValidColorNameContents(char c) { // Match contents of valid color name. if (c == '#') { return true; } if (c >= 'a' && c <= 'z') { return true; } if (c >= 'A' && c <= 'Z') { return true; } if (c >= '0' && c <= '9') { return true; } return false; } } }