forked from DOBRO/binbo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
binbo_pgn.erl
113 lines (94 loc) · 4.04 KB
/
binbo_pgn.erl
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
%% Copyright (c) 2019-2020, Sergei Semichev <chessvegas@chessvegas.com>. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(binbo_pgn).
-export([get_moves/1]).
%%%------------------------------------------------------------------------------
%%% Types
%%%------------------------------------------------------------------------------
-type pgn() :: binary().
-type movelist() :: [binary()].
-type pgn_error() :: empty_pgn | invalid_pgn_datatype.
-export_type([pgn/0, pgn_error/0]).
%%%------------------------------------------------------------------------------
%%% API
%%%------------------------------------------------------------------------------
%% get_moves/1
-spec get_moves(pgn()) -> {ok, movelist()} | {error, pgn_error()}.
get_moves(<<>>) ->
{error, empty_pgn};
get_moves(Pgn) when is_binary(Pgn) ->
Steps = [delete_headers, replace_newlines, delete_comments, delete_ravs, delete_movenums, delete_nags],
get_moves(Steps, Pgn);
get_moves(_) ->
{error, invalid_pgn_datatype}.
%%%------------------------------------------------------------------------------
%%% Internal functions
%%%------------------------------------------------------------------------------
%% get_moves/2
-spec get_moves([Step], pgn()) -> {ok, movelist()} when
Step :: delete_headers | replace_newlines | delete_comments | delete_ravs | delete_movenums | delete_nags.
get_moves([], Pgn) ->
Movelist = uef_bin:split(Pgn, <<$\s>>, 'trim_all'),
{ok, maybe_drop_result(Movelist)};
get_moves([Step | Tail], Pgn) ->
Pgn2 = case Step of
delete_headers -> delete_headers(Pgn);
replace_newlines -> replace_newlines(Pgn);
delete_comments -> delete_comments(Pgn);
delete_ravs -> delete_ravs(Pgn);
delete_movenums -> delete_movenums(Pgn);
delete_nags -> delete_nags(Pgn)
end,
get_moves(Tail, Pgn2).
%% delete_headers/1
-spec delete_headers(pgn()) -> pgn().
delete_headers(Pgn) ->
re:replace(Pgn, <<".*\\]">>, <<>>, [{return, binary}, dotall, never_utf]).
%% replace_newlines/1
-spec replace_newlines(pgn()) -> pgn().
replace_newlines(Pgn) ->
re:replace(Pgn, <<"(\\R|\\t)+">>, <<$\s>>, [{return, binary}, global, bsr_anycrlf, never_utf]).
%% delete_comments/1
-spec delete_comments(pgn()) -> pgn().
delete_comments(Pgn) ->
re:replace(Pgn, <<"(\\{[^}]+\\})+?">>, <<>>, [{return, binary}, global, never_utf]).
%% delete_ravs/1
%% Deletes Recursive Annotation Variations (RAVs)
%% https://chess.stackexchange.com/questions/18214/valid-pgn-variations
-spec delete_ravs(pgn()) -> pgn().
delete_ravs(Pgn) ->
% This PCRE pattern solves the nested parentheses problem.
% As a reference see 'Recursive Patterns' section in OTP docs for 're' module:
% http://erlang.org/doc/man/re.html#recursive-patterns
re:replace(Pgn, <<"\\(([^()]++|(?R))*\\)">>, <<>>, [{return, binary}, global, never_utf]).
%% delete_movenums/1
-spec delete_movenums(pgn()) -> pgn().
delete_movenums(Pgn) ->
re:replace(Pgn, <<"\\d+\\.+">>, <<>>, [{return, binary}, global, never_utf]).
%% delete_nags/1
%% Deletes Numeric Annotation Glyphs (NAGs)
%% https://en.wikipedia.org/wiki/Numeric_Annotation_Glyphs
-spec delete_nags(pgn()) -> pgn().
delete_nags(Pgn) ->
re:replace(Pgn, <<"\\$\\d+">>, <<>>, [{return, binary}, global, never_utf]).
%% maybe_drop_result/1
%% Drops game result, the last element in move list.
-spec maybe_drop_result(movelist()) -> movelist().
maybe_drop_result([]) -> [];
maybe_drop_result(Movelist) ->
[Result|Tail] = lists:reverse(Movelist),
case lists:member(Result, [<<"*">>, <<"1-0">>, <<"0-1">>, <<"1/2-1/2">>]) of
true -> lists:reverse(Tail);
false -> Movelist
end.