1 # -*- coding: iso-8859-1 -*-
6 __all__ = [ 'parse' , 'printTree' ]
9 # expr := <item> <item>* { <expr>* } | <item> <item>* ';'
10 # item := [_a-zA-Z][0-9a-zA-Z]* | '...' | "..."
14 # >>> confparser.printTree( confparser.parse( 'foo; bar;\nc; sub { sub-foo; sub-bar; }' ) )
25 # FIXME: Build "preparsed" file ?
27 # TODO: Rewrite all of this more clearly
29 def advancePosition( text , x , y ) :
31 p = text.rfind( '\n' )
35 y += text.count( '\n' )
36 x = len( text ) - ( p + 1 ) + 1
48 p = s.find( '\\' , pos )
49 if p == -1 or p == len( s ) - 1 :
52 c = unslashMap.get( c , c )
53 s = s[ : p ] + c + s[ p + 2 : ]
61 # keyword: [a-z0-9\-_]+
62 # string: \'(?:\\\'|[^\\\'])+\'
63 # string: "(?:\\\"|[^\\"])+"
69 # FIXME: Instead of truncating self.str, keep a position ?
71 def __init__( self , str ) :
76 self.reBlank = re.compile( r'^\s*' )
77 self.reParser = re.compile( '^'
79 ';' # end-of-statement
81 r'\{' # start-of-block
85 r'(?:[-_.a-z0-9]+|\*)' # identifier
87 r"'[^']*'" # quoted string
89 r"'''.+?'''" # quoted string
91 r'"(?:\\"|[^"])*"' # quoted string
97 def next( self , __empty = [ None , None , None ] ) :
99 r = self.reBlank.search( self.str )
102 self.colNumber , self.lineNumber = advancePosition( blank , self.colNumber , self.lineNumber )
103 self.str = self.str[ r.end( 0 ) : ]
105 if self.str == '' : return __empty
107 # Match the next token
108 r = self.reParser.search( self.str )
109 if r == None : return [ False , self.lineNumber , self.colNumber ]
111 # Remove parsed text from the buffer
112 self.str = self.str[ r.end( 0 ) : ]
116 # Keep current position
117 tokenLine = self.lineNumber
118 tokenColumn = self.colNumber
120 # Advance position after token
121 self.colNumber , self.lineNumber = advancePosition( token , self.colNumber , self.lineNumber )
123 # Return the token and its position
124 return token , tokenLine , tokenColumn
127 # Parse configuration
129 def parse( str , relax = False , warn = False , meta = None ) :
131 stack = [ ( 'root' , [] , [] , ( None , None , meta ) ) ]
134 tok = Tokeniser( str )
135 lastLine , lastColumn = 0 , 0
137 item , line , column = tok.next()
138 if item == None : break
140 raise Exception( 'Syntax error at line %s, column %s' % ( line , column ) )
143 if item.startswith( '#' ) : continue
145 if column == 1 and len( stack ) > 1 and item != '}' :
146 while len( stack ) > 1 :
148 stack = stack[ : -1 ]
149 if cmd[ 0 ] != 'discard' :
150 stack[ -1 ][ 2 ].append( cmd )
152 print '** Error recovered before line %s (missing `}\' ?)' % line
154 if not newElement and cmd != None :
155 raise Exception( 'Missing semicolon before line %s, column %s' % ( line , column ) )
157 stack = stack[ : -1 ]
158 if len( stack ) == 0 :
159 raise Exception( 'Unexpected } at line %s, column %s' % ( line , column ) )
160 if cmd[ 0 ] != 'discard' :
161 stack[ -1 ][ 2 ].append( cmd )
164 if item in [ ';' , '{' , '}' ] :
165 raise Exception( 'Unexpected token `%s\' at line %s, column %s' % ( item , line , column ) )
166 elif item.find( '\n' ) != -1 :
167 raise Exception( 'Unexpected newline character at line %s, column %s' % ( line , column + item.find( '\n' ) ) )
168 cmd = ( item , [] , [] , ( line , column , meta ) )
171 stack[ -1 ][ 2 ].append( cmd )
178 if item.startswith( "'''" ) :
179 item = item[ 3 : -3 ]
180 elif item.startswith( '"' ) :
181 item = unslash( item[ 1 : -1 ] )
182 elif item.startswith( "'" ) :
183 item = item[ 1 : -1 ]
184 if item.find( '\n' ) != -1 :
185 print '** Warning: string with newline character(s)'
186 cmd[ 1 ].append( item )
187 if len( stack ) != 1 or not newElement :
188 raise Exception( 'Unexpected end of file (last token was at line %s, column %s)' % ( lastLine , lastColumn ) )
192 # Helper function to dump configuration
194 def printTreeInner( t , prt = sys.stdout.write , prefix = '' ) :
200 if len( t[ 2 ] ) > 0 :
207 prt( '%r:' % t[ 3 ][ 2 ] )
208 prt( '%s:%s' % ( t[ 3 ][ 0 ] , t[ 3 ][ 1 ] ) )
211 if len( t[ 2 ] ) > 0 :
213 printTreeInner( sub , prt , prefix + ' ' )
222 if len( t[ 2 ] ) > 0 :
224 printTreeInner( sub )