Integers are now handled correctly by printTree.
[confparser-old] / basicparser.py
1 # -*- coding: iso-8859-1 -*-
2
3 #
4 # Basic parser
5 # Copyright (C) 2005  Frédéric Jolliton <frederic@jolliton.com>
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 #
21
22 import re
23 import types
24
25 #-----------------------------------------------------------------------------
26
27 class ParserError( Exception ) : pass
28
29 class Parser :
30
31         def __init__( self , tokens , text ) :
32
33                 self.tokenMatches = {}
34                 for k , v in tokens.items() :
35                         if type( v ) in types.StringTypes :
36                                 v = re.compile( v )
37                         self.tokenMatches[ k ] = v
38                 self.text = text
39                 self.pos = 0
40                 self.tokensToIgnore = ()
41                 self.x = 1
42                 self.y = 1
43
44         def ignore( self , *tokens ) :
45
46                 '''Declare tokens 'tokens' as tokens to ignore when
47                 parsing.'''
48
49                 self.tokensToIgnore = tokens
50
51         def peek( self , *set ) :
52
53                 '''Try to match any of the defined tokens (or only
54                 those from set 'set' if specified.) All matchings tokens
55                 are returned, ordered (by decreasing order) by length.'''
56
57                 tokens = []
58                 if not set :
59                         set = self.tokenMatches.keys()
60                 for tokenName in set :
61                         tokenMatch = self.tokenMatches.get( tokenName )
62                         if tokenMatch :
63                                 m = tokenMatch.match( self.text , self.pos )
64                                 if m :
65                                         tk = m.group( 0 )
66                                         tokens.append( ( tokenName , tk ) )
67                 tokens.sort( lambda a , b : cmp( len( a[ 1 ] ) , len( b[ 1 ] ) ) )
68                 return tokens
69
70         def advance( self , n ) :
71
72                 '''Update current position (line,column) for the 'n'
73                 next characters (if 'n' is an integer, or for the length of
74                 the 'n' string otherwise.)'''
75
76                 if type( n ) in types.StringTypes :
77                         n = len( n )
78                 p = self.text.rfind( '\n' , self.pos , self.pos + n )
79                 if p == -1 :
80                         self.x += n
81                 else :
82                         self.x = self.pos + n - p
83                         self.y += self.text.count( '\n' , self.pos , self.pos + n )
84                 self.pos += n
85
86         def snext( self , *set ) :
87
88                 '''Try to match the longest string matching defined tokens
89                 (or only from one of set 'set' if specified.) If no tokens
90                 match, None is returned.'''
91
92                 set += self.tokensToIgnore
93                 r = None
94                 while 1 :
95                         r = self.peek( *set )
96                         if not r :
97                                 break
98                         r = r[ 0 ]
99                         self.advance( r[ 1 ] )
100                         if r[ 0 ] not in self.tokensToIgnore :
101                                 break
102                 return r
103
104         def next( self , *set ) :
105
106                 '''Try to match the longest string matching defined tokens
107                 (or only from one of set 'set' if specified.) If no tokens
108                 match, an exception is raised.'''
109
110                 r = self.snext( *set )
111                 if not r :
112                         r = self.peek()
113                         if r :
114                                 misc = 'found %r but ' % r[ 0 ][ 1 ]
115                         else :
116                                 misc = ''
117                         raise ParserError( '%s, %sexpected one of the following tokens: %r'
118                                 % ( self.getPos() , misc , list( set ) ) )
119                 return r
120
121         def getPos( self ) :
122
123                 '''Return current position in a texture representation.'''
124
125                 return 'at line %d, column %d' % ( self.y , self.x )
126
127         def point( self ) :
128
129                 '''Return a multiple-line string to show where an error
130                 happened.'''
131
132                 lineStart = self.text.rfind( '\n' , 0 , self.pos + 1 ) + 1
133                 lineEnd = self.text.find( '\n' , self.pos + 1 )
134                 if lineEnd == -1 :
135                         lineEnd = len( self.text )
136                 prefix = 'line %s: ' % self.y
137                 r = ''
138                 r += prefix + self.text[ lineStart : lineEnd ].replace( '\t' , ' ' ) + '\n'
139                 r += prefix + ' ' * ( self.pos - lineStart ) + '^' + '\n'
140                 return r