[parsing] Add support for advanced MFM (ISH-257)
This commit is contained in:
parent
dc2d65d799
commit
fa81be967a
3 changed files with 88 additions and 12 deletions
|
@ -56,9 +56,13 @@ public static class MfmSerializer
|
|||
{
|
||||
result.Append("$[");
|
||||
result.Append(mfmFnNode.Name);
|
||||
result.Append('.');
|
||||
var args = mfmFnNode.Args.Select(p => p.Value != null ? $"{p.Key}={p.Value}" : $"{p.Key}");
|
||||
result.Append(string.Join(',', args));
|
||||
if (mfmFnNode.Args is { } args)
|
||||
{
|
||||
result.Append('.');
|
||||
var str = args.Value.Select(p => p.Value != null ? $"{p.Key}={p.Value.Value}" : $"{p.Key}");
|
||||
result.Append(string.Join(',', str));
|
||||
}
|
||||
|
||||
result.Append(' ');
|
||||
result.Append(Serialize(node.Children));
|
||||
result.Append(']');
|
||||
|
|
|
@ -92,11 +92,10 @@ module MfmNodeTypes =
|
|||
member val Url = url
|
||||
member val Silent = silent
|
||||
|
||||
type MfmFnNode(args: Dictionary<string, string>, name, children) =
|
||||
type MfmFnNode(name, args: IDictionary<string, string option> option, children) =
|
||||
inherit MfmInlineNode(children)
|
||||
// (string, bool) args = (string, null as string?)
|
||||
member val Args = args
|
||||
member val Name = name
|
||||
member val Args = args
|
||||
|
||||
type internal MfmCharNode(v: char) =
|
||||
inherit MfmInlineNode([])
|
||||
|
@ -163,6 +162,18 @@ module private MfmParser =
|
|||
| None -> user
|
||||
| Some v -> user + "@" + v
|
||||
|
||||
let fnArg =
|
||||
many1Chars asciiLetter
|
||||
.>>. opt (
|
||||
pchar '='
|
||||
>>. manyCharsTill anyChar (nextCharSatisfies <| fun p -> p = ',' || isWhitespace p)
|
||||
)
|
||||
|
||||
let fnDict (input: (string * string option) list option) : IDictionary<string, string option> option =
|
||||
match input with
|
||||
| None -> None
|
||||
| Some items -> items |> dict |> Some
|
||||
|
||||
// References
|
||||
let node, nodeRef = createParserForwardedToRef ()
|
||||
let inlineNode, inlineNodeRef = createParserForwardedToRef ()
|
||||
|
@ -228,6 +239,13 @@ module private MfmParser =
|
|||
>>. manyCharsTill (satisfy isAsciiLetter <|> satisfy isDigit <|> anyOf "+-_") (skipChar ':')
|
||||
|>> fun e -> MfmEmojiCodeNode(e) :> MfmNode
|
||||
|
||||
let fnNode =
|
||||
skipString "$[" >>. many1Chars asciiLower
|
||||
.>>. opt (skipChar '.' >>. sepBy1 fnArg (skipChar ','))
|
||||
.>> skipChar ' '
|
||||
.>>. many1Till inlineNode (skipChar ']')
|
||||
|>> fun ((n, o), c) -> MfmFnNode(n, fnDict o, aggregateTextInline c) :> MfmNode
|
||||
|
||||
let plainNode =
|
||||
skipString "<plain>" >>. manyCharsTill anyChar (skipString "</plain>")
|
||||
|>> fun v -> MfmPlainNode(v) :> MfmNode
|
||||
|
@ -241,7 +259,8 @@ module private MfmParser =
|
|||
|>> fun c -> MfmCenterNode(aggregateTextInline c) :> MfmNode
|
||||
|
||||
let mentionNode =
|
||||
(previousCharSatisfiesNot isNotWhitespace <|> previousCharSatisfies (isAnyOf <| "()"))
|
||||
(previousCharSatisfiesNot isNotWhitespace
|
||||
<|> previousCharSatisfies (isAnyOf <| "()"))
|
||||
>>. skipString "@"
|
||||
>>. many1Chars (
|
||||
satisfy isLetterOrNumber
|
||||
|
@ -337,9 +356,10 @@ module private MfmParser =
|
|||
linkNode
|
||||
mathNode
|
||||
emojiCodeNode
|
||||
fnNode
|
||||
charNode ]
|
||||
|
||||
//TODO: still missing: FnNode, MfmSearchNode
|
||||
//TODO: still missing: FnNode
|
||||
|
||||
let blockNodeSeq =
|
||||
[ plainNode; centerNode; smallNode; codeBlockNode; mathBlockNode; quoteNode ]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Serialization;
|
||||
using Iceshrimp.Parsing;
|
||||
using Microsoft.FSharp.Collections;
|
||||
using Microsoft.FSharp.Core;
|
||||
using static Iceshrimp.Parsing.MfmNodeTypes;
|
||||
|
||||
namespace Iceshrimp.Tests.Parsing;
|
||||
|
@ -38,9 +39,7 @@ public class MfmTests
|
|||
{
|
||||
List<MfmNode> expected =
|
||||
[
|
||||
new MfmInlineCodeNode("test"),
|
||||
new MfmCodeBlockNode("test", null),
|
||||
new MfmCodeBlockNode("test", "lang")
|
||||
new MfmInlineCodeNode("test"), new MfmCodeBlockNode("test", null), new MfmCodeBlockNode("test", "lang")
|
||||
];
|
||||
|
||||
var res = Mfm.parse("""
|
||||
|
@ -298,6 +297,54 @@ public class MfmTests
|
|||
MfmSerializer.Serialize(res).Should().BeEquivalentTo(canonical);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFn()
|
||||
{
|
||||
const string input =
|
||||
"test $[] $[test] $[test ] $[test test] $[test.a test] $[test.a=b test] $[test.a=b,c=e test] $[test.a,c=e test] $[test.a=b,c test]";
|
||||
|
||||
var some = FSharpOption<IDictionary<string, FSharpOption<string>>>.Some;
|
||||
var none = FSharpOption<IDictionary<string, FSharpOption<string>>>.None;
|
||||
var test = ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("test")]);
|
||||
|
||||
// @formatter:off
|
||||
List<MfmNode> expected =
|
||||
[
|
||||
new MfmTextNode("test $[] $[test] $[test ] "),
|
||||
new MfmFnNode("test",
|
||||
none,
|
||||
test),
|
||||
new MfmTextNode(" "),
|
||||
new MfmFnNode("test",
|
||||
some(new Dictionary<string, FSharpOption<string>>{ {"a", FSharpOption<string>.None} }),
|
||||
test),
|
||||
new MfmTextNode(" "),
|
||||
new MfmFnNode("test",
|
||||
some(new Dictionary<string, FSharpOption<string>>{ {"a", FSharpOption<string>.Some("b")} }),
|
||||
test),
|
||||
new MfmTextNode(" "),
|
||||
new MfmFnNode("test",
|
||||
some(new Dictionary<string, FSharpOption<string>>{ {"a", FSharpOption<string>.Some("b")}, {"c", FSharpOption<string>.Some("e")} }),
|
||||
test),
|
||||
new MfmTextNode(" "),
|
||||
new MfmFnNode("test",
|
||||
some(new Dictionary<string, FSharpOption<string>>{ {"a", FSharpOption<string>.None}, {"c", FSharpOption<string>.Some("e")} }),
|
||||
test),
|
||||
new MfmTextNode(" "),
|
||||
new MfmFnNode("test",
|
||||
some(new Dictionary<string, FSharpOption<string>>{ {"a", FSharpOption<string>.Some("b")}, {"c", FSharpOption<string>.None} }),
|
||||
test),
|
||||
];
|
||||
// @formatter:on
|
||||
|
||||
var res = Mfm.parse(input);
|
||||
|
||||
AssertionOptions.FormattingOptions.MaxDepth = 100;
|
||||
res.ToList().Should().Equal(expected, MfmNodeEqual);
|
||||
|
||||
MfmSerializer.Serialize(res).Should().BeEquivalentTo(input);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Benchmark()
|
||||
{
|
||||
|
@ -416,8 +463,13 @@ public class MfmTests
|
|||
case MfmFnNode ax:
|
||||
{
|
||||
var bx = (MfmFnNode)b;
|
||||
if (ax.Args != bx.Args) return false;
|
||||
if (ax.Name != bx.Name) return false;
|
||||
if ((ax.Args == null) != (bx.Args == null)) return false;
|
||||
if (ax.Args == null || bx.Args == null) return true;
|
||||
if (ax.Args.Value.Count != bx.Args.Value.Count) return false;
|
||||
// ReSharper disable once UsageOfDefaultStructEquality
|
||||
if (ax.Args.Value.Except(bx.Args.Value).Any()) return false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue