// Copyright: Wieger Wesselink 2026, <wieger at 10x10 dot org>
//
// Distributed under the Boost Software License, Version 1.0.
// http://www.boost.org/LICENSE_1_0.txt
//
// Portable Draughts Notation (PDN) Reading Grammar — Peggy / PEG.js
//
// This is a scannerless PEG grammar: there is no separate tokenizing pass
// that commits to a token via maximal munch before the parser rules run.
// That changes how the "DRAW2 (1-1) vs numeric move" subtlety shows up:
// other implementations stop DRAW2 from eating the leading digits of a move
// like "1-10" (a lexer concern). Here the risk runs the other way — without
// a guard, NormalMove (Square MOVESEPARATOR Square) would happily parse the
// literal text "1-1" as a move from square 1 to square 1, which is
// otherwise unreachable syntax (draughts never moves a piece onto its own
// square). Move excludes that exact literal so "1-1" is always read as
// DRAW2, matching every other implementation's maximal-munch tokenizer.

PdnFile
  = _ Game (GameSeparator Game)* GameSeparator?

GameSeparator
  = ASTERISK
  / Result

Game
  = GameHeader GameBody?
  / GameBody

GameHeader
  = PdnTag+

GameBody
  = (GameMove / Variation / COMMENT / SETUP / NAG)+

PdnTag
  = LBRACKET IDENTIFIER STRING RBRACKET

GameMove
  = MOVENUMBER? Move MOVESTRENGTH?

Variation
  = LPAREN GameBody RPAREN

Move
  = !Draw2Literal NormalMove
  / CaptureMove
  / ALPHAMOVE
  / ELLIPSES

NormalMove
  = Square MOVESEPARATOR Square

CaptureMove
  = Square (CAPTURESEPARATOR Square)+

Square
  = ALPHASQUARE
  / NUMSQUARE

Result
  = Result1
  / Result2

Result1
  = WIN1
  / DRAW1
  / LOSS1

Result2
  = WIN2
  / DRAW2
  / LOSS2
  / DOUBLEFORFEIT

// ---------------------------------------------------------------------------
// Tokens
//
// PEG.js/Peggy is scannerless, so each token rule consumes its own trailing
// whitespace/comments (via `_`) instead of relying on a separate whitespace
// channel like TPG's `separator` or ANTLR4's `-> skip`.
// ---------------------------------------------------------------------------

WIN1            = "1-0" _
DRAW1           = "1/2-1/2" _
LOSS1           = "0-1" _
WIN2            = "2-0" _
DRAW2           = Draw2Literal _
LOSS2           = "0-2" _
DOUBLEFORFEIT   = "0-0" _

// Bare literal shared by DRAW2 and the negative lookahead in Move.
Draw2Literal
  = "1-1" !DIGIT

ELLIPSES        = "..." _
// MOVENUMBER: digits followed by one dot, optionally followed by two more
// dots, giving "1." or "1..." (move continuation indicator).
MOVENUMBER      = DIGIT+ "." ".."? _

MOVESTRENGTH    = "(" [!?]+ ")" _
                / [!?]+ _
NAG             = "$" DIGIT+ _

// ALPHAMOVE before ALPHASQUARE so a 4-character square pair like "e2f3"
// isn't left half-consumed; PEG backtracking makes the order immaterial
// here, but it documents the intent.
ALPHAMOVE       = [a-h][1-8][a-h][1-8] _
ALPHASQUARE     = [a-h][1-8] _
// Squares: 1-9, 10-50, and 01-09 (zero-prefixed, used in some game types).
NUMSQUARE       = [1-9][0-9]? _
                / "0"[1-9] _

MOVESEPARATOR   = "-" _
CAPTURESEPARATOR = [x:] _

LPAREN          = "(" _
RPAREN          = ")" _
LBRACKET        = "[" _
RBRACKET        = "]" _
ASTERISK        = "*" _

// SETUP: a setup string delimited by forward slashes, e.g. /W:Wk50.B:B1,2/.
SETUP           = "/" [^/]* "/" _
// STRING: double-quoted; backslash escapes any following character.
STRING          = "\"" ("\\" . / [^"])* "\"" _
// COMMENT: not nested — the first '}' closes it.
COMMENT         = "{" [^}]* "}" _
// Tag identifiers start with an uppercase letter.
IDENTIFIER      = [A-Z][a-zA-Z0-9_]* _

DIGIT           = [0-9]

// Whitespace and '%'-line comments, skipped after every token.
_ "whitespace"
  = ([ \t\n\r] / "%" [^\n\r]*)*
