sgf_parser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.irbrc +3 -0
- data/.rvmrc +4 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +17 -0
- data/LICENSE +20 -0
- data/README.textile +27 -0
- data/Rakefile +20 -0
- data/TODO +0 -0
- data/VERSION +1 -0
- data/bin/sgf +28 -0
- data/bin/stm2dot +18 -0
- data/doc/sgf_state_machine.dot +58 -0
- data/doc/sgf_state_machine.svg +269 -0
- data/lib/sgf.rb +15 -0
- data/lib/sgf/binary_file_error.rb +4 -0
- data/lib/sgf/debugger.rb +15 -0
- data/lib/sgf/default_event_listener.rb +37 -0
- data/lib/sgf/model/constants.rb +19 -0
- data/lib/sgf/model/event_listener.rb +72 -0
- data/lib/sgf/model/game.rb +52 -0
- data/lib/sgf/model/label.rb +12 -0
- data/lib/sgf/model/node.rb +115 -0
- data/lib/sgf/model/property_handler.rb +51 -0
- data/lib/sgf/more/state_machine_presenter.rb +43 -0
- data/lib/sgf/more/stm_dot_converter.rb +139 -0
- data/lib/sgf/parse_error.rb +25 -0
- data/lib/sgf/parser.rb +56 -0
- data/lib/sgf/renderer.rb +25 -0
- data/lib/sgf/sgf_helper.rb +55 -0
- data/lib/sgf/sgf_state_machine.rb +174 -0
- data/lib/sgf/state_machine.rb +76 -0
- data/sgf_parser.gemspec +95 -0
- data/spec/fixtures/2009-11-01-1.sgf +24 -0
- data/spec/fixtures/2009-11-01-2.sgf +23 -0
- data/spec/fixtures/chinese_gb.sgf +9 -0
- data/spec/fixtures/chinese_utf.sgf +9 -0
- data/spec/fixtures/example.sgf +18 -0
- data/spec/fixtures/good.sgf +55 -0
- data/spec/fixtures/good1.sgf +167 -0
- data/spec/fixtures/kgs.sgf +723 -0
- data/spec/fixtures/test.png +0 -0
- data/spec/sgf/model/event_listener_spec.rb +97 -0
- data/spec/sgf/model/game_spec.rb +29 -0
- data/spec/sgf/model/node_spec.rb +84 -0
- data/spec/sgf/more/state_machine_presenter_spec.rb +29 -0
- data/spec/sgf/parse_error_spec.rb +10 -0
- data/spec/sgf/parser_spec.rb +210 -0
- data/spec/sgf/sgf_helper_spec.rb +68 -0
- data/spec/sgf/sgf_state_machine_spec.rb +166 -0
- data/spec/sgf/state_machine_spec.rb +137 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +47 -0
- metadata +150 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
module SGF
|
2
|
+
module More
|
3
|
+
class StmDotConverter
|
4
|
+
|
5
|
+
STATE_PROPERTIES = {
|
6
|
+
SGF::SGFStateMachine::STATE_BEGIN => {
|
7
|
+
:shape => 'circle', :fillcolor => '#44ff44', :style => 'filled'
|
8
|
+
},
|
9
|
+
SGF::SGFStateMachine::STATE_GAME_BEGIN => {
|
10
|
+
},
|
11
|
+
SGF::SGFStateMachine::STATE_VAR_BEGIN => {
|
12
|
+
},
|
13
|
+
SGF::SGFStateMachine::STATE_VAR_END => {
|
14
|
+
},
|
15
|
+
SGF::SGFStateMachine::STATE_GAME_END => {
|
16
|
+
:label => 'end', :shape => 'doublecircle', :fillcolor => '#44ff44', :style => 'filled'
|
17
|
+
},
|
18
|
+
SGF::SGFStateMachine::STATE_INVALID => {
|
19
|
+
:label => 'error', :shape => 'octagon', :fillcolor => '#ff4444', :style => 'filled'
|
20
|
+
},
|
21
|
+
}
|
22
|
+
|
23
|
+
EDGE_PROPERTIES = {
|
24
|
+
"begin:game_begin" => {
|
25
|
+
:weight => 100
|
26
|
+
},
|
27
|
+
"game_begin:game_node" => {
|
28
|
+
:weight => 100
|
29
|
+
},
|
30
|
+
"game_node:prop_name_begin" => {
|
31
|
+
:weight => 100
|
32
|
+
},
|
33
|
+
"prop_name_begin:prop_name" => {
|
34
|
+
:weight => 100
|
35
|
+
},
|
36
|
+
"prop_name:value_begin" => {
|
37
|
+
:weight => 100
|
38
|
+
},
|
39
|
+
"value_begin:value" => {
|
40
|
+
:weight => 100
|
41
|
+
},
|
42
|
+
"value:value_end" => {
|
43
|
+
:weight => 100
|
44
|
+
},
|
45
|
+
"value_end:var_end" => {
|
46
|
+
:weight => 100
|
47
|
+
},
|
48
|
+
"var_end:game_node" => {
|
49
|
+
:weight => 100
|
50
|
+
},
|
51
|
+
"var_end:game_end" => {
|
52
|
+
:weight => 100
|
53
|
+
},
|
54
|
+
}
|
55
|
+
|
56
|
+
def process stm
|
57
|
+
s = "digraph SGF_STATE_MACHINE{"
|
58
|
+
s << graph_attributes
|
59
|
+
s << create_node_for_state(stm.start_state)
|
60
|
+
|
61
|
+
stm.transitions.each do |start_state, transitions|
|
62
|
+
transitions.each do |transition|
|
63
|
+
s << create_edge_for_transition(start_state, transition)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
s << "}\n"
|
68
|
+
s
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def graph_attributes
|
74
|
+
'
|
75
|
+
{rank = same; begin;}
|
76
|
+
{rank = same; game_begin;}
|
77
|
+
{rank = same; var_begin game_node;}
|
78
|
+
{rank = same; prop_name_begin;}
|
79
|
+
{rank = same; prop_name;}
|
80
|
+
{rank = same; value_begin;}
|
81
|
+
{rank = same; value value_escape;}
|
82
|
+
{rank = same; value_end;}
|
83
|
+
{rank = same; var_end;}
|
84
|
+
{rank = same; game_end invalid;}
|
85
|
+
'
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_node_for_state state
|
89
|
+
@processed_states ||= []
|
90
|
+
return "" if @processed_states.include?(state)
|
91
|
+
|
92
|
+
@processed_states << state
|
93
|
+
|
94
|
+
s = state.to_s + "["
|
95
|
+
properties = STATE_PROPERTIES[state]
|
96
|
+
if properties
|
97
|
+
prop_arr = []
|
98
|
+
properties.each do |key, value|
|
99
|
+
prop_arr << "#{key} = #{value.inspect}"
|
100
|
+
end
|
101
|
+
s << prop_arr.join(',')
|
102
|
+
end
|
103
|
+
s << "];\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_edge_for_transition start_state, transition
|
107
|
+
s = ""
|
108
|
+
s << create_node_for_state(start_state)
|
109
|
+
|
110
|
+
end_state = transition.after_state || start_state
|
111
|
+
s << create_node_for_state(end_state)
|
112
|
+
|
113
|
+
s << start_state.to_s << " -> " << end_state.to_s << "["
|
114
|
+
if end_state == SGFStateMachine::STATE_INVALID
|
115
|
+
s << "color=\"#FFBBBB\", weight=-100"
|
116
|
+
else
|
117
|
+
s << "label=\"" << (transition.description || pattern_to_label(transition.event_pattern)) << "\""
|
118
|
+
end
|
119
|
+
|
120
|
+
edge_properties = EDGE_PROPERTIES["#{start_state}:#{end_state}"]
|
121
|
+
if edge_properties
|
122
|
+
edge_properties.each do |key, value|
|
123
|
+
s << ", #{key}=#{value.inspect}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
s << "];\n"
|
128
|
+
end
|
129
|
+
|
130
|
+
def pattern_to_label pattern
|
131
|
+
if pattern.nil?
|
132
|
+
"EOS"
|
133
|
+
else
|
134
|
+
pattern.inspect[1..-2].gsub("\\", "\\\\")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SGF
|
2
|
+
class ParseError < StandardError
|
3
|
+
attr_reader :input, :position, :description
|
4
|
+
|
5
|
+
def initialize input, position, description = nil
|
6
|
+
@input = input
|
7
|
+
@position = position
|
8
|
+
@description = description || "SGF parse error occurred here "
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
if @position > 1000
|
13
|
+
start_position = @position - 1000
|
14
|
+
s = '...'
|
15
|
+
else
|
16
|
+
start_position = 0
|
17
|
+
s = ''
|
18
|
+
end
|
19
|
+
s << @input[start_position..@position]
|
20
|
+
s << ' <=== '
|
21
|
+
s << description.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/sgf/parser.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module SGF
|
2
|
+
class Parser
|
3
|
+
attr_reader :event_listener
|
4
|
+
|
5
|
+
def initialize event_listener
|
6
|
+
@event_listener = event_listener
|
7
|
+
@stm = SGFStateMachine.new
|
8
|
+
@stm.context = event_listener
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse input
|
12
|
+
@stm.reset
|
13
|
+
|
14
|
+
raise ArgumentError.new if input.nil? or input.strip.empty?
|
15
|
+
|
16
|
+
input.strip!
|
17
|
+
|
18
|
+
0.upto(input.size - 1) do |i|
|
19
|
+
@position = i
|
20
|
+
@stm.event(input[i,1])
|
21
|
+
end
|
22
|
+
|
23
|
+
@stm.end
|
24
|
+
rescue StateMachineError => e
|
25
|
+
raise ParseError.new(input, @position, e.message)
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_file filename
|
29
|
+
raise BinaryFileError if Parser.is_binary?(filename)
|
30
|
+
File.open(filename) do |f|
|
31
|
+
parse f.readlines.join
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def parse input, debug = false
|
37
|
+
parser = SGF::Parser.new(SGF::Model::EventListener.new(debug))
|
38
|
+
parser.parse input
|
39
|
+
parser.event_listener.game
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_file filename, debug = false
|
43
|
+
parser = SGF::Parser.new(SGF::Model::EventListener.new(debug))
|
44
|
+
parser.parse_file filename
|
45
|
+
parser.event_listener.game
|
46
|
+
end
|
47
|
+
|
48
|
+
def is_binary? file
|
49
|
+
# This does not work?!
|
50
|
+
# parts = %x(/usr/bin/file -i #{file}).split(':', 2)
|
51
|
+
# not parts[1].include?('text')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/lib/sgf/renderer.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module SGF
|
2
|
+
# Renderer is called by a client program to render client object in SGF format.
|
3
|
+
class Renderer
|
4
|
+
|
5
|
+
def render_node node
|
6
|
+
raise 'INCOMPLETE'
|
7
|
+
result = ""
|
8
|
+
|
9
|
+
if type == NODE_SETUP
|
10
|
+
unless black_moves.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
unless white_moves.empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
else
|
17
|
+
result << (color == BLACK ? "B" : "W")
|
18
|
+
result << "[" << "" << "]"
|
19
|
+
end
|
20
|
+
|
21
|
+
result << "C[" << comment << "]" unless comment.nil?
|
22
|
+
result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module SGF
|
2
|
+
module SGFHelper
|
3
|
+
def xy_to_sgf_pos x, y
|
4
|
+
SGF::Model::Constants::POSITIONS[x, 1] + SGF::Model::Constants::POSITIONS[y, 1]
|
5
|
+
end
|
6
|
+
|
7
|
+
def move_to_sgf color, x, y
|
8
|
+
return "" unless [SGF::Model::Constants::BLACK, SGF::Model::Constants::WHITE].include?(color)
|
9
|
+
|
10
|
+
sgf = ";"
|
11
|
+
sgf << (color == SGF::Model::Constants::WHITE ? "W" : "B")
|
12
|
+
sgf << "["
|
13
|
+
sgf << xy_to_sgf_pos(x, y)
|
14
|
+
sgf << "]"
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_position_array input
|
18
|
+
raise ArgumentError.new(input) if input.nil? or input !~ /^\s*\w\w(:\w\w)?\s*$/
|
19
|
+
|
20
|
+
input.strip!
|
21
|
+
input.downcase!
|
22
|
+
|
23
|
+
if input.include?(':')
|
24
|
+
parts = input.split(':', 2)
|
25
|
+
position1 = to_position(parts[0])
|
26
|
+
position2 = to_position(parts[1])
|
27
|
+
positions = []
|
28
|
+
(position1[0]..position2[0]).each do |i|
|
29
|
+
(position1[1]..position2[1]).each do |j|
|
30
|
+
positions << [i, j]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
positions
|
34
|
+
else
|
35
|
+
[to_position(input)]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_position input
|
40
|
+
raise ArgumentError.new(input) if input.nil? or input.strip.length != 2
|
41
|
+
|
42
|
+
input.strip!
|
43
|
+
input.downcase!
|
44
|
+
|
45
|
+
[input[0] - ?a, input[1] - ?a]
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_label input
|
49
|
+
raise ArgumentError.new(input) if input.nil? or input !~ /^\s*\w\w:\w+\s*$/
|
50
|
+
|
51
|
+
position, text = input.split(':', 2)
|
52
|
+
SGF::Model::Label.new(to_position(position), text.strip)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module SGF
|
2
|
+
class StateMachineError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class SGFStateMachine < StateMachine
|
6
|
+
|
7
|
+
STATE_BEGIN = :begin
|
8
|
+
STATE_GAME_BEGIN = :game_begin
|
9
|
+
STATE_GAME_END = :game_end
|
10
|
+
STATE_NODE = :game_node
|
11
|
+
STATE_VAR_BEGIN = :var_begin
|
12
|
+
STATE_VAR_END = :var_end
|
13
|
+
STATE_PROP_NAME_BEGIN = :prop_name_begin
|
14
|
+
STATE_PROP_NAME = :prop_name
|
15
|
+
STATE_VALUE_BEGIN = :value_begin
|
16
|
+
STATE_VALUE = :value
|
17
|
+
STATE_VALUE_ESCAPE = :value_escape
|
18
|
+
STATE_VALUE_END = :value_end
|
19
|
+
STATE_INVALID = :invalid
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
super(STATE_BEGIN)
|
23
|
+
|
24
|
+
start_game = lambda{ |stm| return if stm.context.nil?; stm.context.start_game }
|
25
|
+
end_game = lambda{ |stm| return if stm.context.nil?; stm.context.end_game }
|
26
|
+
start_node = lambda{ |stm| return if stm.context.nil?; stm.context.start_node }
|
27
|
+
start_variation = lambda{ |stm| return if stm.context.nil?; stm.context.start_variation }
|
28
|
+
store_input_in_buffer = lambda{ |stm| return if stm.context.nil?; stm.buffer = stm.input }
|
29
|
+
append_input_to_buffer = lambda{ |stm| return if stm.context.nil?; stm.buffer += stm.input }
|
30
|
+
set_property_name = lambda{ |stm| return if stm.context.nil?; stm.context.property_name = stm.buffer; stm.clear_buffer }
|
31
|
+
set_property_value = lambda{ |stm| return if stm.context.nil?; stm.context.property_value = stm.buffer; stm.clear_buffer }
|
32
|
+
end_variation = lambda{ |stm| return if stm.context.nil?; stm.context.end_variation }
|
33
|
+
report_error = lambda{ |stm| raise StateMachineError.new('SGF Error near "' + stm.input + '"') }
|
34
|
+
|
35
|
+
transition STATE_BEGIN,
|
36
|
+
/\(/,
|
37
|
+
STATE_GAME_BEGIN,
|
38
|
+
start_game
|
39
|
+
|
40
|
+
transition [STATE_GAME_BEGIN, STATE_VAR_END, STATE_VALUE_END],
|
41
|
+
/;/,
|
42
|
+
STATE_NODE,
|
43
|
+
start_node
|
44
|
+
|
45
|
+
transition STATE_VAR_BEGIN,
|
46
|
+
/;/,
|
47
|
+
STATE_NODE
|
48
|
+
|
49
|
+
transition [STATE_NODE, STATE_VAR_END, STATE_VALUE_END],
|
50
|
+
/\(/,
|
51
|
+
STATE_VAR_BEGIN,
|
52
|
+
start_variation
|
53
|
+
|
54
|
+
transition [STATE_NODE, STATE_VALUE_END],
|
55
|
+
/[a-zA-Z]/,
|
56
|
+
STATE_PROP_NAME_BEGIN,
|
57
|
+
store_input_in_buffer
|
58
|
+
|
59
|
+
transition [STATE_PROP_NAME_BEGIN, STATE_PROP_NAME],
|
60
|
+
/[a-zA-Z]/,
|
61
|
+
STATE_PROP_NAME,
|
62
|
+
append_input_to_buffer
|
63
|
+
|
64
|
+
transition [STATE_PROP_NAME_BEGIN, STATE_PROP_NAME],
|
65
|
+
/\[/,
|
66
|
+
STATE_VALUE_BEGIN,
|
67
|
+
set_property_name
|
68
|
+
|
69
|
+
transition STATE_VALUE_END,
|
70
|
+
/\[/,
|
71
|
+
STATE_VALUE_BEGIN
|
72
|
+
|
73
|
+
transition STATE_VALUE_BEGIN,
|
74
|
+
/[^\]]/,
|
75
|
+
STATE_VALUE,
|
76
|
+
store_input_in_buffer
|
77
|
+
|
78
|
+
transition [STATE_VALUE_BEGIN, STATE_VALUE],
|
79
|
+
/\\/,
|
80
|
+
STATE_VALUE_ESCAPE
|
81
|
+
|
82
|
+
transition STATE_VALUE_ESCAPE,
|
83
|
+
/./,
|
84
|
+
STATE_VALUE,
|
85
|
+
append_input_to_buffer
|
86
|
+
|
87
|
+
transition STATE_VALUE,
|
88
|
+
/[^\]]/,
|
89
|
+
nil,
|
90
|
+
append_input_to_buffer
|
91
|
+
|
92
|
+
transition [STATE_VALUE_BEGIN, STATE_VALUE],
|
93
|
+
/\]/,
|
94
|
+
STATE_VALUE_END,
|
95
|
+
set_property_value
|
96
|
+
|
97
|
+
transition STATE_VAR_END,
|
98
|
+
nil,
|
99
|
+
STATE_GAME_END,
|
100
|
+
end_game
|
101
|
+
|
102
|
+
transition [STATE_NODE, STATE_VALUE_END, STATE_VAR_END],
|
103
|
+
/\)/,
|
104
|
+
STATE_VAR_END,
|
105
|
+
end_variation
|
106
|
+
|
107
|
+
transition [STATE_BEGIN, STATE_GAME_BEGIN, STATE_NODE, STATE_VAR_BEGIN, STATE_VAR_END, STATE_PROP_NAME_BEGIN, STATE_PROP_NAME, STATE_VALUE_END],
|
108
|
+
/[^\s]/,
|
109
|
+
STATE_INVALID,
|
110
|
+
report_error
|
111
|
+
end
|
112
|
+
|
113
|
+
# # Overwrite parent to get better performance!
|
114
|
+
# def event input
|
115
|
+
# case @state
|
116
|
+
# when STATE_NODE
|
117
|
+
# if input > "A" and input < "Z" or input > "a" and input < "z"
|
118
|
+
# @state = STATE_PROP_NAME_BEGIN
|
119
|
+
# self.buffer = input
|
120
|
+
# return
|
121
|
+
# end
|
122
|
+
# when STATE_VALUE_BEGIN
|
123
|
+
# if input == "]"
|
124
|
+
# self.state = STATE_VALUE_END
|
125
|
+
# context.property_value = buffer if context
|
126
|
+
# clear_buffer
|
127
|
+
# return
|
128
|
+
# elsif input != "\\"
|
129
|
+
# self.state = STATE_VALUE
|
130
|
+
# self.buffer = input
|
131
|
+
# return
|
132
|
+
# end
|
133
|
+
# when STATE_VALUE
|
134
|
+
# if input == "]"
|
135
|
+
# self.state = STATE_VALUE_END
|
136
|
+
# context.property_value = buffer if context
|
137
|
+
# clear_buffer
|
138
|
+
# return
|
139
|
+
# elsif input != "\\"
|
140
|
+
# self.buffer += input
|
141
|
+
# return
|
142
|
+
# end
|
143
|
+
# when STATE_VALUE_END
|
144
|
+
# if input == "\n"
|
145
|
+
# return
|
146
|
+
# elsif input == ";"
|
147
|
+
# self.state = STATE_NODE
|
148
|
+
# context.start_node if context
|
149
|
+
# return
|
150
|
+
# elsif input > "A" and input < "Z" or input > "a" and input < "z"
|
151
|
+
# @state = STATE_PROP_NAME_BEGIN
|
152
|
+
# self.buffer = input
|
153
|
+
# return
|
154
|
+
# end
|
155
|
+
# when STATE_PROP_NAME_BEGIN, STATE_PROP_NAME
|
156
|
+
# if input == "["
|
157
|
+
# self.state = STATE_VALUE_BEGIN
|
158
|
+
# context.property_name = buffer if context
|
159
|
+
# clear_buffer
|
160
|
+
# else
|
161
|
+
# self.buffer += input
|
162
|
+
# end
|
163
|
+
# return
|
164
|
+
# when STATE_VALUE_ESCAPE
|
165
|
+
# self.state = STATE_VALUE
|
166
|
+
# self.buffer += input
|
167
|
+
# return
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# #puts input.inspect
|
171
|
+
# super input
|
172
|
+
# end
|
173
|
+
end
|
174
|
+
end
|