Skip to content
Draft
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
7 changes: 7 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
load("@rules_java//java:java_library.bzl", "java_library")

java_library(
name = "pgn",
srcs = glob(["**/*.java"]),
visibility = ["//visibility:public"],
)
47 changes: 47 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/PgnReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.muchq.pgn;

import com.muchq.pgn.lexer.PgnLexer;
import com.muchq.pgn.model.PgnGame;
import com.muchq.pgn.parser.PgnParser;

import java.util.List;

/**
* High-level API for parsing PGN strings.
*
* Usage:
* PgnGame game = PgnReader.parseGame(pgnString);
* List<PgnGame> games = PgnReader.parseAll(pgnString);
*/
public final class PgnReader {

private PgnReader() {
// Utility class
}

/**
* Parse a single game from PGN text.
*
* @param pgn The PGN string
* @return The parsed game
*/
public static PgnGame parseGame(String pgn) {
var lexer = new PgnLexer(pgn);
var tokens = lexer.tokenize();
var parser = new PgnParser(tokens);
return parser.parseGame();
}

/**
* Parse all games from PGN text.
*
* @param pgn The PGN string (may contain multiple games)
* @return List of parsed games
*/
public static List<PgnGame> parseAll(String pgn) {
var lexer = new PgnLexer(pgn);
var tokens = lexer.tokenize();
var parser = new PgnParser(tokens);
return parser.parseAll();
}
}
23 changes: 23 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/lexer/LexerException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.muchq.pgn.lexer;

/**
* Exception thrown when the lexer encounters invalid input.
*/
public class LexerException extends RuntimeException {
private final int line;
private final int column;

public LexerException(String message, int line, int column) {
super(String.format("%s at line %d, column %d", message, line, column));
this.line = line;
this.column = column;
}

public int getLine() {
return line;
}

public int getColumn() {
return column;
}
}
29 changes: 29 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/lexer/PgnLexer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.muchq.pgn.lexer;

import java.util.List;

/**
* Tokenizes PGN input into a list of tokens.
*
* Usage:
* PgnLexer lexer = new PgnLexer(pgnString);
* List<Token> tokens = lexer.tokenize();
*/
public class PgnLexer {
private final String input;

public PgnLexer(String input) {
this.input = input;
}

/**
* Tokenize the input and return all tokens.
* The last token will always be EOF.
*
* @return List of tokens
* @throws LexerException if invalid input is encountered
*/
public List<Token> tokenize() {
throw new UnsupportedOperationException("TODO: implement");
}
}
17 changes: 17 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/lexer/Token.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.muchq.pgn.lexer;

/**
* A token produced by the lexer.
*
* @param type The type of token
* @param value The string value of the token
* @param line The line number (1-indexed)
* @param column The column number (1-indexed)
*/
public record Token(TokenType type, String value, int line, int column) {

@Override
public String toString() {
return String.format("%s('%s') at %d:%d", type, value, line, column);
}
}
28 changes: 28 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/lexer/TokenType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.muchq.pgn.lexer;

public enum TokenType {
// Delimiters
LEFT_BRACKET, // [
RIGHT_BRACKET, // ]
LEFT_PAREN, // (
RIGHT_PAREN, // )

// Literals
STRING, // "quoted string"
INTEGER, // 1, 2, 15, etc.
SYMBOL, // Tag names, moves (e4, Nf3, O-O, O-O-O)

// Move notation
PERIOD, // .
ELLIPSIS, // ...

// Annotations
NAG, // $1, $2, etc.
COMMENT, // {comment text}

// Game results
RESULT, // 1-0, 0-1, 1/2-1/2, *

// End of file
EOF
}
13 changes: 13 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/model/File.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.muchq.pgn.model;

public enum File {
A, B, C, D, E, F, G, H;

public static File fromChar(char c) {
throw new UnsupportedOperationException("TODO: implement");
}

public char toChar() {
throw new UnsupportedOperationException("TODO: implement");
}
}
22 changes: 22 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/model/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.muchq.pgn.model;

public enum GameResult {
WHITE_WINS("1-0"),
BLACK_WINS("0-1"),
DRAW("1/2-1/2"),
ONGOING("*");

private final String notation;

GameResult(String notation) {
this.notation = notation;
}

public String notation() {
return notation;
}

public static GameResult fromNotation(String s) {
throw new UnsupportedOperationException("TODO: implement");
}
}
27 changes: 27 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/model/Move.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.muchq.pgn.model;

import java.util.List;
import java.util.Optional;

/**
* Represents a single move in PGN notation with optional annotations.
*
* @param san The Standard Algebraic Notation for the move (e.g., "e4", "Nf3", "O-O")
* @param comment Optional comment in curly braces
* @param nags Numeric Annotation Glyphs ($1, $2, etc.)
* @param variations Alternative lines (recursive)
*/
public record Move(
String san,
Optional<String> comment,
List<Nag> nags,
List<List<Move>> variations
) {
public Move(String san) {
this(san, Optional.empty(), List.of(), List.of());
}

public Move(String san, String comment) {
this(san, Optional.of(comment), List.of(), List.of());
}
}
23 changes: 23 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/model/Nag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.muchq.pgn.model;

/**
* Numeric Annotation Glyph - standard annotations like $1 (good move), $2 (poor move), etc.
* Common NAGs:
* $1 = ! (good move)
* $2 = ? (poor move)
* $3 = !! (very good move)
* $4 = ?? (blunder)
* $5 = !? (interesting move)
* $6 = ?! (dubious move)
*/
public record Nag(int value) {

public static Nag parse(String s) {
throw new UnsupportedOperationException("TODO: implement");
}

@Override
public String toString() {
return "$" + value;
}
}
23 changes: 23 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/model/PgnGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.muchq.pgn.model;

import java.util.List;
import java.util.Optional;

/**
* A complete parsed PGN game.
*/
public record PgnGame(
List<TagPair> tags,
List<Move> moves,
GameResult result
) {
/**
* Get a tag value by name.
*/
public Optional<String> getTag(String name) {
return tags.stream()
.filter(t -> t.name().equals(name))
.map(TagPair::value)
.findFirst();
}
}
24 changes: 24 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/model/Piece.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.muchq.pgn.model;

public enum Piece {
KING('K'),
QUEEN('Q'),
ROOK('R'),
BISHOP('B'),
KNIGHT('N'),
PAWN('\0');

private final char symbol;

Piece(char symbol) {
this.symbol = symbol;
}

public char symbol() {
return symbol;
}

public static Piece fromSymbol(char c) {
throw new UnsupportedOperationException("TODO: implement");
}
}
27 changes: 27 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/model/Rank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.muchq.pgn.model;

public enum Rank {
R1(1), R2(2), R3(3), R4(4), R5(5), R6(6), R7(7), R8(8);

private final int number;

Rank(int number) {
this.number = number;
}

public int number() {
return number;
}

public static Rank fromNumber(int n) {
throw new UnsupportedOperationException("TODO: implement");
}

public static Rank fromChar(char c) {
throw new UnsupportedOperationException("TODO: implement");
}

public char toChar() {
throw new UnsupportedOperationException("TODO: implement");
}
}
13 changes: 13 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/model/Square.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.muchq.pgn.model;

public record Square(File file, Rank rank) {

public static Square parse(String s) {
throw new UnsupportedOperationException("TODO: implement");
}

@Override
public String toString() {
throw new UnsupportedOperationException("TODO: implement");
}
}
7 changes: 7 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/model/TagPair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.muchq.pgn.model;

/**
* A PGN tag pair like [Event "World Championship"]
*/
public record TagPair(String name, String value) {
}
25 changes: 25 additions & 0 deletions jvm/src/main/java/com/muchq/pgn/parser/ParseException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.muchq.pgn.parser;

import com.muchq.pgn.lexer.Token;

/**
* Exception thrown when the parser encounters invalid input.
*/
public class ParseException extends RuntimeException {
private final Token token;

public ParseException(String message, Token token) {
super(String.format("%s at line %d, column %d (token: %s)",
message, token.line(), token.column(), token.value()));
this.token = token;
}

public ParseException(String message) {
super(message);
this.token = null;
}

public Token getToken() {
return token;
}
}
Loading
Loading