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.
Files changed (53) hide show
  1. data/.irbrc +3 -0
  2. data/.rvmrc +4 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +17 -0
  5. data/LICENSE +20 -0
  6. data/README.textile +27 -0
  7. data/Rakefile +20 -0
  8. data/TODO +0 -0
  9. data/VERSION +1 -0
  10. data/bin/sgf +28 -0
  11. data/bin/stm2dot +18 -0
  12. data/doc/sgf_state_machine.dot +58 -0
  13. data/doc/sgf_state_machine.svg +269 -0
  14. data/lib/sgf.rb +15 -0
  15. data/lib/sgf/binary_file_error.rb +4 -0
  16. data/lib/sgf/debugger.rb +15 -0
  17. data/lib/sgf/default_event_listener.rb +37 -0
  18. data/lib/sgf/model/constants.rb +19 -0
  19. data/lib/sgf/model/event_listener.rb +72 -0
  20. data/lib/sgf/model/game.rb +52 -0
  21. data/lib/sgf/model/label.rb +12 -0
  22. data/lib/sgf/model/node.rb +115 -0
  23. data/lib/sgf/model/property_handler.rb +51 -0
  24. data/lib/sgf/more/state_machine_presenter.rb +43 -0
  25. data/lib/sgf/more/stm_dot_converter.rb +139 -0
  26. data/lib/sgf/parse_error.rb +25 -0
  27. data/lib/sgf/parser.rb +56 -0
  28. data/lib/sgf/renderer.rb +25 -0
  29. data/lib/sgf/sgf_helper.rb +55 -0
  30. data/lib/sgf/sgf_state_machine.rb +174 -0
  31. data/lib/sgf/state_machine.rb +76 -0
  32. data/sgf_parser.gemspec +95 -0
  33. data/spec/fixtures/2009-11-01-1.sgf +24 -0
  34. data/spec/fixtures/2009-11-01-2.sgf +23 -0
  35. data/spec/fixtures/chinese_gb.sgf +9 -0
  36. data/spec/fixtures/chinese_utf.sgf +9 -0
  37. data/spec/fixtures/example.sgf +18 -0
  38. data/spec/fixtures/good.sgf +55 -0
  39. data/spec/fixtures/good1.sgf +167 -0
  40. data/spec/fixtures/kgs.sgf +723 -0
  41. data/spec/fixtures/test.png +0 -0
  42. data/spec/sgf/model/event_listener_spec.rb +97 -0
  43. data/spec/sgf/model/game_spec.rb +29 -0
  44. data/spec/sgf/model/node_spec.rb +84 -0
  45. data/spec/sgf/more/state_machine_presenter_spec.rb +29 -0
  46. data/spec/sgf/parse_error_spec.rb +10 -0
  47. data/spec/sgf/parser_spec.rb +210 -0
  48. data/spec/sgf/sgf_helper_spec.rb +68 -0
  49. data/spec/sgf/sgf_state_machine_spec.rb +166 -0
  50. data/spec/sgf/state_machine_spec.rb +137 -0
  51. data/spec/spec.opts +4 -0
  52. data/spec/spec_helper.rb +47 -0
  53. 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
@@ -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