Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--example.sgf30
-rw-r--r--sgf.g40
-rw-r--r--sgfparser.py100
3 files changed, 170 insertions, 0 deletions
diff --git a/example.sgf b/example.sgf
new file mode 100644
index 0000000..cfba1bd
--- /dev/null
+++ b/example.sgf
@@ -0,0 +1,30 @@
+(
+;GM[1]FF[3]
+RU[Japanese]SZ[19]HA[0]KM[5.5]
+PW[White]
+PB[Black]
+GN[Copyright goproblems.com]
+DT[1999-07-21]TM[1800]
+;C[Black to play and live.]
+AW[bb][cb][cc][cd][de][df][cg][ch][dh][ai][bi][ci]
+AB[ba][ab][ac][bc][bd][be][cf][bg][bh]
+(;B[af];W[ah]
+(;B[ce];W[ag] C[only one eye this way])
+
+(;B[ag];W[ce])
+)
+
+(;B[ah];W[af]
+(;B[ae];W[bf];B[ag];W[bf]
+(;B[af];W[ce] C[oops! you can't take this stone])
+
+(;B[ce];W[af];B[bg] C[RIGHT black plays under the stones and lives])
+)
+
+(;B[bf];W[ae])
+)
+
+(;B[ae];W[ag])
+
+)
+
diff --git a/sgf.g b/sgf.g
new file mode 100644
index 0000000..3629f56
--- /dev/null
+++ b/sgf.g
@@ -0,0 +1,40 @@
+parser SGF:
+ ignore: '\\s+'
+
+
+ ### Go-specific tokens: http://www.red-bean.com/sgf/go.html ###
+ token Point: '[a-zA-Z]{2}'
+ token Move: '[a-zA-Z]{2}'
+ token Stone: '[a-zA-Z]{2}'
+
+ ### SGF Tokens as specified in http://www.red-bean.com/sgf/sgf4.html ###
+
+ token PropID: '([a-z]*[A-Z]){1,2}[a-z]*'
+ token Number: '[+-]?[0-9]+'
+ token Real: '[+-]?[0-9]+(\\.[0-9]+)?'
+ token Color: '(B|W)'
+ token Text: '[^\\]]*' # In YAPPS, the longest matches take precedence,
+ # if they're both the same length, then the
+ # first one listed in the grammar is used.
+
+
+ rule GameTree: "\\(" {{ res = [] }}
+ (Node {{ res.append(Node) }})+
+ (GameTree {{ res.append(GameTree) }})*
+ "\\)" {{ return res }}
+
+ rule Node: ";" {{ res = [] }}
+ (Property {{ res.append(Property) }}
+ )+ {{ return dict(res) }}
+
+ rule Property: PropID {{ res = (PropID, []) }}
+ ("\\[" ValueType "\\]" {{ res[1].append(ValueType) }}
+ )+ {{ return res }}
+
+ rule ValueType: Number {{ return int(Number) }}
+ | Real {{ return float(Real) }}
+ | Color {{ return Color }}
+ | Move {{ return (Move[0].islower() and ord(Move[0])-96 or ord(Move[0])-64, Move[1].islower() and ord(Move[1])-96 or ord(Move[1])-64) }}
+ | Text {{ return Text }}
+ | '' {{ return None }}
+
diff --git a/sgfparser.py b/sgfparser.py
new file mode 100644
index 0000000..6b52522
--- /dev/null
+++ b/sgfparser.py
@@ -0,0 +1,100 @@
+# Begin -- grammar generated by Yapps
+import sys, re
+from yapps import runtime
+
+class SGFScanner(runtime.Scanner):
+ patterns = [
+ ("''", re.compile('')),
+ ('"\\\\]"', re.compile('\\]')),
+ ('"\\\\["', re.compile('\\[')),
+ ('";"', re.compile(';')),
+ ('"\\\\)"', re.compile('\\)')),
+ ('"\\\\("', re.compile('\\(')),
+ ('\\s+', re.compile('\\s+')),
+ ('Point', re.compile('[a-zA-Z]{2}')),
+ ('Move', re.compile('[a-zA-Z]{2}')),
+ ('Stone', re.compile('[a-zA-Z]{2}')),
+ ('PropID', re.compile('([a-z]*[A-Z]){1,2}[a-z]*')),
+ ('Number', re.compile('[+-]?[0-9]+')),
+ ('Real', re.compile('[+-]?[0-9]+(\\.[0-9]+)?')),
+ ('Color', re.compile('(B|W)')),
+ ('Text', re.compile('[^\\]]*')),
+ ]
+ def __init__(self, str,*args,**kw):
+ runtime.Scanner.__init__(self,None,{'\\s+':None,},str,*args,**kw)
+
+class SGF(runtime.Parser):
+ Context = runtime.Context
+ def GameTree(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, 'GameTree', [])
+ self._scan('"\\\\("', context=_context)
+ res = []
+ while 1:
+ Node = self.Node(_context)
+ res.append(Node)
+ if self._peek('";"', '"\\\\)"', '"\\\\("', context=_context) != '";"': break
+ while self._peek('"\\\\)"', '"\\\\("', context=_context) == '"\\\\("':
+ GameTree = self.GameTree(_context)
+ res.append(GameTree)
+ self._scan('"\\\\)"', context=_context)
+ return res
+
+ def Node(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, 'Node', [])
+ self._scan('";"', context=_context)
+ res = []
+ while 1:
+ Property = self.Property(_context)
+ res.append(Property)
+ if self._peek('PropID', '";"', '"\\\\)"', '"\\\\("', context=_context) != 'PropID': break
+ return dict(res)
+
+ def Property(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, 'Property', [])
+ PropID = self._scan('PropID', context=_context)
+ res = (PropID, [])
+ while 1:
+ self._scan('"\\\\["', context=_context)
+ ValueType = self.ValueType(_context)
+ self._scan('"\\\\]"', context=_context)
+ res[1].append(ValueType)
+ if self._peek('"\\\\["', 'PropID', '";"', '"\\\\)"', '"\\\\("', context=_context) != '"\\\\["': break
+ return res
+
+ def ValueType(self, _parent=None):
+ _context = self.Context(_parent, self._scanner, 'ValueType', [])
+ _token = self._peek('Number', 'Real', 'Color', 'Move', 'Text', "''", context=_context)
+ if _token == 'Number':
+ Number = self._scan('Number', context=_context)
+ return int(Number)
+ elif _token == 'Real':
+ Real = self._scan('Real', context=_context)
+ return float(Real)
+ elif _token == 'Color':
+ Color = self._scan('Color', context=_context)
+ return Color
+ elif _token == 'Move':
+ Move = self._scan('Move', context=_context)
+ return (Move[0].islower() and ord(Move[0])-96 or ord(Move[0])-64, Move[1].islower() and ord(Move[1])-96 or ord(Move[1])-64)
+ elif _token == 'Text':
+ Text = self._scan('Text', context=_context)
+ return Text
+ else: # == "''"
+ self._scan("''", context=_context)
+ return None
+
+
+def parse(rule, text):
+ P = SGF(SGFScanner(text))
+ return runtime.wrap_error_reporter(P, rule)
+
+if __name__ == '__main__':
+ from sys import argv, stdin
+ if len(argv) >= 2:
+ if len(argv) >= 3:
+ f = open(argv[2],'r')
+ else:
+ f = stdin
+ print parse(argv[1], f.read())
+ else: print >>sys.stderr, 'Args: <rule> [<filename>]'
+# End -- grammar generated by Yapps