Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,43 @@

import Parsing

private struct SpecialCommentParser: Parser {
var body: some Parser<Substring, PGNComment> {
OneOf {
CALParser()
CSLParser()
EMTParser()
EvalParser()
CLKParser()
}
}
}

struct MultipleCommentParser: Parser {
var body: some Parser<Substring, [PGNComment]> {
"{"
Whitespace()
Many {
OneOf{
CALParser()
CSLParser()
EMTParser()
EvalParser()
CLKParser()
}
} separator: {
OneOf {
Parse {
"{"
Whitespace()
SpecialCommentParser()
Many {
SpecialCommentParser()
} separator: {
Whitespace()
}
Optionally {
Whitespace(1...)
Prefix { $0 != "}" }
.map(String.init)
}
Whitespace()
"}"
}.map { firstComment, remainingComments, text in
var comments = [firstComment]
comments.append(contentsOf: remainingComments)
if let text, !text.isEmpty {
comments.append(.text(text))
}
return comments
}
Whitespace()
"}"
}
}
97 changes: 97 additions & 0 deletions Tests/FischerCoreTests/PGN/CommentListParserTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// CommentListParserTests.swift
// FischerCore
//
// Created by Codex on 15/5/26.
//

import Parsing
import Testing
@testable import FischerCore

struct CommentListParserTests {
@Test("Parses adjacent CSL and CAL annotations followed by text")
func parsesAdjacentSquareAndArrowAnnotationsWithText() throws {
let comments = try CommentListParser().parse(
"{[%csl Rh7][%cal Gd3h7,Gh2h7] amenazando mate!}"
)

#expect(comments == [
.squareList([
PGNSquare(color: .red, square: .h7),
]),
.arrowList([
PGNArrow(color: .green, fromSquare: .d3, toSquare: .h7),
PGNArrow(color: .green, fromSquare: .h2, toSquare: .h7),
]),
.text("amenazando mate!"),
])
}

@Test("Parses adjacent annotations with multiple arrows and long text")
func parsesAdjacentAnnotationsWithMultipleArrowsAndLongText() throws {
let comments = try CommentListParser().parse(
"{[%csl Gg3][%cal Gd1h1,Gg4g5,Rf4h6,Gc3e2,Ge2g3] con una ventaja clara. Plan: Th1-Ce2 y Cg3-g5.}"
)

#expect(comments == [
.squareList([
PGNSquare(color: .green, square: .g3),
]),
.arrowList([
PGNArrow(color: .green, fromSquare: .d1, toSquare: .h1),
PGNArrow(color: .green, fromSquare: .g4, toSquare: .g5),
PGNArrow(color: .red, fromSquare: .f4, toSquare: .h6),
PGNArrow(color: .green, fromSquare: .c3, toSquare: .e2),
PGNArrow(color: .green, fromSquare: .e2, toSquare: .g3),
]),
.text("con una ventaja clara. Plan: Th1-Ce2 y Cg3-g5."),
])
}

@Test("Preserves adjacent CSL and CAL annotations without text")
func parsesAdjacentSquareAndArrowAnnotationsWithoutText() throws {
let comments = try CommentListParser().parse(
"{ [%csl Gf6][%cal Bg8f6,Yd7f6] }"
)

#expect(comments == [
.squareList([
PGNSquare(color: .green, square: .f6),
]),
.arrowList([
PGNArrow(color: .blue, fromSquare: .g8, toSquare: .f6),
PGNArrow(color: .yellow, fromSquare: .d7, toSquare: .f6),
]),
])
}

@Test("PGN game parser keeps adjacent annotations and text in move comments")
func pgnGameParserKeepsAdjacentAnnotationsAndTextInMoveComments() throws {
let input =
"""
[Event "Mixed comment annotations"]
[Site "Local"]
[Date "2026.05.15"]
[Round "-"]
[White "-"]
[Black "-"]
[Result "*"]

1. e4 {[%csl Rh7][%cal Gd3h7,Gh2h7] amenazando mate!} *
"""

let game = try PGNGameParser().parse(input)

#expect(game.elements[0].postWhiteCommentList == [
.squareList([
PGNSquare(color: .red, square: .h7),
]),
.arrowList([
PGNArrow(color: .green, fromSquare: .d3, toSquare: .h7),
PGNArrow(color: .green, fromSquare: .h2, toSquare: .h7),
]),
.text("amenazando mate!"),
])
}
}
Loading