File size: 3,502 Bytes
466436b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502af73
 
466436b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502af73
466436b
 
502af73
466436b
 
 
 
 
 
 
 
 
 
 
 
 
 
502af73
466436b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502af73
466436b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502af73
 
466436b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502af73
466436b
 
 
 
 
 
 
 
502af73
466436b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502af73
466436b
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
 * TGN Parser TypeScript Wrapper
 *
 * Wraps the jison-generated parser with TypeScript types
 *
 * Based on lotus project architecture:
 * - Use jison npm package for grammar compilation at build time
 * - Generate parser to .js file in build phase
 * - Use synchronous parsing (no async needed)
 * - Works in both browser and Node.js environments
 */

/**
 * Parsed move action - represents a single player's action in a round
 */
export interface ParsedMoveAction {
	type: "move" | "pass" | "resign";
	position?: string; // ab0yz coordinate notation
}

/**
 * Parsed move round - contains both black and white moves
 */
export interface ParsedMoveRound {
	round: number;
	action_black: ParsedMoveAction;
	action_white?: ParsedMoveAction;
}

/**
 * Parsed game result
 */
export interface ParsedGameResult {
	Result: string; // "black win" | "white win" | "draw" | "unknown"
	Conquer?: {
		n: number;
		unit: string; // "points" | "stones"
	};
}

/**
 * Parsed TGN tags (metadata)
 */
export interface ParsedTags {
	Event?: string;
	Site?: string;
	Date?: string;
	Round?: string;
	Black?: string;
	White?: string;
	Result?: string;
	Board?: number[]; // [x, y, z] or [x, y]
	Handicap?: string;
	Rules?: string;
	TimeControl?: string;
	Annotator?: string;
	Application?: string;
	[key: string]: string | number[] | ParsedGameResult | undefined;
}

/**
 * Parser output structure
 */
export interface TGNParseResult {
	tags: ParsedTags;
	moves: ParsedMoveRound[] | null;
	success: boolean;
}

/**
 * Parser error with position information
 */
export class TGNParseError extends Error {
	constructor(
		message: string,
		public line?: number,
		public column?: number,
		public hash?: any
	) {
		super(message);
		this.name = "TGNParseError";
	}
}

// Will be set by initialization code or build process
let parserModule: any = null;

/**
 * Set the parser module (called by initialization code)
 * This allows the pre-built parser to be used
 */
export function setParserModule(module: any): void {
	parserModule = module;
}

/**
 * Get the parser module
 * Throws error if parser not loaded
 */
function getParser() {
	if (!parserModule) {
		throw new Error(
			"TGN parser not loaded. Please ensure the parser has been built.\n" +
				"Run: npm run build:parsers"
		);
	}
	return parserModule;
}

/**
 * Parse TGN string and return structured data
 * Synchronous parsing (no async needed)
 *
 * @param tgnString - TGN formatted game notation
 * @returns Parsed game data with tags and moves
 * @throws TGNParseError if parsing fails
 */
export function parseTGN(tgnString: string): TGNParseResult {
	const parser = getParser();

	if (!parser.parse) {
		throw new Error("TGN parser parse method not available");
	}

	try {
		const result = parser.parse(tgnString);
		return result as TGNParseResult;
	} catch (error: any) {
		// Wrap jison errors with our custom error type
		throw new TGNParseError(
			error.message || "Unknown parse error",
			error.hash?.line,
			error.hash?.loc?.first_column,
			error.hash
		);
	}
}

/**
 * Validate TGN string without fully parsing
 * Synchronous validation (no async needed)
 *
 * @param tgnString - TGN formatted game notation
 * @returns Object with valid flag and error message if invalid
 */
export function validateTGN(tgnString: string): { valid: boolean; error?: string } {
	try {
		parseTGN(tgnString);
		return { valid: true };
	} catch (error: any) {
		return {
			valid: false,
			error: error.message || "Unknown validation error"
		};
	}
}